diff options
Diffstat (limited to 'tests')
497 files changed, 29553 insertions, 16769 deletions
diff --git a/tests/ActivityManagerPerfTests/stub-app/Android.bp b/tests/ActivityManagerPerfTests/stub-app/Android.bp new file mode 100644 index 000000000000..a3c1f5b2f17d --- /dev/null +++ b/tests/ActivityManagerPerfTests/stub-app/Android.bp @@ -0,0 +1,68 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_test_helper_app { + name: "ActivityManagerPerfTestsStubApp1", + static_libs: ["ActivityManagerPerfTestsUtils"], + srcs: [ + "src/**/*.java", + ], + resource_dirs: [ + "app1/res", + "res", + ], + platform_apis: true, + certificate: "platform", + aaptflags: [ + "--rename-manifest-package com.android.stubs.am1", + "--auto-add-overlay", + ], +} + +android_test_helper_app { + name: "ActivityManagerPerfTestsStubApp2", + static_libs: ["ActivityManagerPerfTestsUtils"], + srcs: [ + "src/**/*.java", + ], + resource_dirs: [ + "app2/res", + "res", + ], + platform_apis: true, + certificate: "platform", + aaptflags: [ + "--rename-manifest-package com.android.stubs.am2", + "--auto-add-overlay", + ], +} + +android_test_helper_app { + name: "ActivityManagerPerfTestsStubApp3", + static_libs: ["ActivityManagerPerfTestsUtils"], + srcs: [ + "src/**/*.java", + ], + resource_dirs: [ + "app3/res", + "res", + ], + platform_apis: true, + certificate: "platform", + aaptflags: [ + "--rename-manifest-package com.android.stubs.am3", + "--auto-add-overlay", + ], +} + diff --git a/tests/ActivityManagerPerfTests/stub-app/AndroidManifest.xml b/tests/ActivityManagerPerfTests/stub-app/AndroidManifest.xml new file mode 100644 index 000000000000..a57f64c320c8 --- /dev/null +++ b/tests/ActivityManagerPerfTests/stub-app/AndroidManifest.xml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.stubs.am"> + + <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/> + <application android:label="Android TestCase" > + <provider + android:authorities="@string/authority" + android:name=".TestContentProvider" + android:exported="true" /> + <receiver + android:name=".TestBroadcastReceiver" + android:exported="true"> + <intent-filter> + <action android:name="com.android.stubs.am.ACTION_BROADCAST_TEST" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </receiver> + <service + android:name=".InitService" + android:exported="true" /> + <service + android:name=".TestService" + android:exported="true" /> + <activity + android:name=".TestActivity" + android:excludeFromRecents="true" + android:turnScreenOn="true" + android:launchMode="singleTask"> + <intent-filter> + <action android:name="com.android.stubs.am.ACTION_START_TEST_ACTIVITY" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> + </application> + +</manifest> + diff --git a/tests/ActivityManagerPerfTests/stub-app/app1/res/values/config.xml b/tests/ActivityManagerPerfTests/stub-app/app1/res/values/config.xml new file mode 100644 index 000000000000..667472db5f83 --- /dev/null +++ b/tests/ActivityManagerPerfTests/stub-app/app1/res/values/config.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="authority" translatable="false">com.android.stubs.am1.testapp</string> +</resources> diff --git a/tests/ActivityManagerPerfTests/stub-app/app2/res/values/config.xml b/tests/ActivityManagerPerfTests/stub-app/app2/res/values/config.xml new file mode 100644 index 000000000000..085273574d95 --- /dev/null +++ b/tests/ActivityManagerPerfTests/stub-app/app2/res/values/config.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="authority" translatable="false">com.android.stubs.am2.testapp</string> +</resources> diff --git a/tests/ActivityManagerPerfTests/stub-app/app3/res/values/config.xml b/tests/ActivityManagerPerfTests/stub-app/app3/res/values/config.xml new file mode 100644 index 000000000000..6895d7258dad --- /dev/null +++ b/tests/ActivityManagerPerfTests/stub-app/app3/res/values/config.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="authority" translatable="false">com.android.stubs.am3.testapp</string> +</resources> diff --git a/tests/RollbackTest/TestApp/res_v1/values/values.xml b/tests/ActivityManagerPerfTests/stub-app/res/layout/activity_content.xml index 0447c74a79a6..f79f006087d7 100644 --- a/tests/RollbackTest/TestApp/res_v1/values/values.xml +++ b/tests/ActivityManagerPerfTests/stub-app/res/layout/activity_content.xml @@ -5,7 +5,7 @@ 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 + 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, @@ -14,7 +14,7 @@ limitations under the License. --> -<resources> - <integer name="app_version">1</integer> - <integer name="split_version">0</integer> -</resources> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/content" + android:layout_width="match_parent" + android:layout_height="match_parent"/> diff --git a/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/InitService.java b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/InitService.java new file mode 100644 index 000000000000..18fdc442bfbf --- /dev/null +++ b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/InitService.java @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.stubs.am; + +import static com.android.frameworks.perftests.am.util.Constants.COMMAND_ACQUIRE_CONTENT_PROVIDER; +import static com.android.frameworks.perftests.am.util.Constants.COMMAND_BIND_SERVICE; +import static com.android.frameworks.perftests.am.util.Constants.COMMAND_RELEASE_CONTENT_PROVIDER; +import static com.android.frameworks.perftests.am.util.Constants.COMMAND_SEND_BROADCAST; +import static com.android.frameworks.perftests.am.util.Constants.COMMAND_START_ACTIVITY; +import static com.android.frameworks.perftests.am.util.Constants.COMMAND_STOP_ACTIVITY; +import static com.android.frameworks.perftests.am.util.Constants.COMMAND_UNBIND_SERVICE; + +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.IContentProvider; +import android.content.Intent; +import android.content.ServiceConnection; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Log; + +import com.android.frameworks.perftests.am.util.Constants; +import com.android.frameworks.perftests.am.util.ICommandReceiver; + +public class InitService extends Service { + private static final String TAG = "InitService"; + public static final boolean VERBOSE = false; + + private static class Stub extends ICommandReceiver.Stub { + private final Context mContext; + private final Messenger mCallback; + private final Handler mHandler; + private final Messenger mMessenger; + final ArrayMap<String, MyServiceConnection> mServices = + new ArrayMap<String, MyServiceConnection>(); + final ArrayMap<Uri, IContentProvider> mProviders = + new ArrayMap<Uri, IContentProvider>(); + + Stub(Context context, Messenger callback) { + mContext = context; + mCallback = callback; + HandlerThread thread = new HandlerThread("result handler"); + thread.start(); + mHandler = new H(thread.getLooper()); + mMessenger = new Messenger(mHandler); + } + + private class H extends Handler { + H(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + if (msg.what == Constants.MSG_DEFAULT) { + if (VERBOSE) { + Log.i(TAG, "H: received seq=" + msg.arg1 + ", result=" + msg.arg2); + } + sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, msg.arg1, msg.arg2, null); + } else if (msg.what == Constants.MSG_UNBIND_DONE) { + if (VERBOSE) { + Log.i(TAG, "H: received unbind=" + msg.obj); + } + synchronized (InitService.sStub) { + Bundle b = (Bundle) msg.obj; + String pkg = b.getString(Constants.EXTRA_SOURCE_PACKAGE, ""); + MyServiceConnection c = mServices.remove(pkg); + if (c != null) { + sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, c.mSeq, + Constants.RESULT_NO_ERROR, null); + } + } + } + } + } + + @Override + public void sendCommand(int command, int seq, String sourcePackage, String targetPackage, + int flags, Bundle bundle) { + if (VERBOSE) { + Log.i(TAG, "Received command=" + command + ", seq=" + seq + ", from=" + + sourcePackage + ", to=" + targetPackage + ", flags=" + flags); + } + switch (command) { + case COMMAND_BIND_SERVICE: + handleBindService(seq, targetPackage, flags, bundle); + break; + case COMMAND_UNBIND_SERVICE: + handleUnbindService(seq, targetPackage); + break; + case COMMAND_ACQUIRE_CONTENT_PROVIDER: + acquireProvider(seq, bundle.getParcelable(Constants.EXTRA_URI)); + break; + case COMMAND_RELEASE_CONTENT_PROVIDER: + releaseProvider(seq, bundle.getParcelable(Constants.EXTRA_URI)); + break; + case COMMAND_SEND_BROADCAST: + sendBroadcast(seq, targetPackage); + break; + case COMMAND_START_ACTIVITY: + startActivity(seq, targetPackage); + break; + case COMMAND_STOP_ACTIVITY: + stopActivity(seq, targetPackage); + break; + } + } + + private void handleBindService(int seq, String targetPackage, int flags, Bundle bundle) { + Intent intent = new Intent(); + intent.setClassName(targetPackage, "com.android.stubs.am.TestService"); + intent.putExtra(Constants.EXTRA_RECEIVER_CALLBACK, mMessenger); + if (bundle != null) { + intent.putExtras(bundle); + } + synchronized (this) { + if (!mServices.containsKey(targetPackage)) { + MyServiceConnection c = new MyServiceConnection(mCallback); + c.mSeq = seq; + if (!mContext.bindService(intent, c, flags)) { + Log.e(TAG, "Unable to bind to service in " + targetPackage); + sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq, + Constants.RESULT_ERROR, null); + } else { + if (VERBOSE) { + Log.i(TAG, "Bind to service " + intent); + } + mServices.put(targetPackage, c); + } + } else { + sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq, + Constants.RESULT_NO_ERROR, null); + } + } + } + + private void handleUnbindService(int seq, String target) { + MyServiceConnection c = null; + synchronized (this) { + c = mServices.get(target); + } + if (c != null) { + c.mSeq = seq; + mContext.unbindService(c); + } + } + + private void acquireProvider(int seq, Uri uri) { + ContentResolver resolver = mContext.getContentResolver(); + IContentProvider provider = resolver.acquireProvider(uri); + if (provider != null) { + synchronized (mProviders) { + mProviders.put(uri, provider); + } + sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq, + Constants.RESULT_NO_ERROR, null); + } else { + sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq, + Constants.RESULT_ERROR, null); + } + } + + private void releaseProvider(int seq, Uri uri) { + ContentResolver resolver = mContext.getContentResolver(); + IContentProvider provider; + synchronized (mProviders) { + provider = mProviders.get(uri); + } + if (provider != null) { + resolver.releaseProvider(provider); + synchronized (mProviders) { + mProviders.remove(uri); + } + } + sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq, + Constants.RESULT_NO_ERROR, null); + } + + private void sendBroadcast(final int seq, String targetPackage) { + Intent intent = new Intent(Constants.STUB_ACTION_BROADCAST); + intent.setPackage(targetPackage); + mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq, + Constants.RESULT_NO_ERROR, null); + } + }, null, 0, null, null); + } + + private void startActivity(int seq, String targetPackage) { + Intent intent = new Intent(Constants.STUB_ACTION_ACTIVITY); + intent.setClassName(targetPackage, "com.android.stubs.am.TestActivity"); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); + intent.putExtra(Constants.EXTRA_ARG1, seq); + intent.putExtra(Constants.EXTRA_RECEIVER_CALLBACK, mMessenger); + mContext.startActivity(intent); + } + + private void stopActivity(int seq, String targetPackage) { + Intent intent = new Intent(Constants.STUB_ACTION_ACTIVITY); + intent.setClassName(targetPackage, "com.android.stubs.am.TestActivity"); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); + intent.putExtra(Constants.EXTRA_REQ_FINISH_ACTIVITY, true); + intent.putExtra(Constants.EXTRA_ARG1, seq); + intent.putExtra(Constants.EXTRA_RECEIVER_CALLBACK, mMessenger); + mContext.startActivity(intent); + } + }; + + private static void sendResult(Messenger callback, int what, int seq, int result, Object obj) { + Message msg = Message.obtain(); + msg.what = what; + msg.arg1 = seq; + msg.arg2 = result; + msg.obj = obj; + try { + if (VERBOSE) { + Log.i(TAG, "Sending result seq=" + seq + ", result=" + result); + } + callback.send(msg); + } catch (RemoteException e) { + Log.e(TAG, "Error in sending result back", e); + } + msg.recycle(); + } + + private static class MyServiceConnection implements ServiceConnection { + private Messenger mCallback; + int mSeq; + + MyServiceConnection(Messenger callback) { + mCallback = callback; + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, mSeq, + Constants.RESULT_NO_ERROR, null); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + synchronized (sStub) { + MyServiceConnection c = sStub.mServices.remove(name.getPackageName()); + if (c != null) { + sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, c.mSeq, + Constants.RESULT_NO_ERROR, null); + } + } + } + } + + private static Stub sStub = null; + + @Override + public IBinder onBind(Intent intent) { + return new Binder(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Messenger callback = intent.getParcelableExtra(Constants.EXTRA_RECEIVER_CALLBACK); + if (sStub == null) { + sStub = new Stub(getApplicationContext(), callback); + } + + Bundle extras = new Bundle(); + extras.putString(Constants.EXTRA_SOURCE_PACKAGE, getPackageName()); + extras.putBinder(Constants.EXTRA_RECEIVER_CALLBACK, sStub); + sendResult(callback, Constants.REPLY_PACKAGE_START_RESULT, + intent.getIntExtra(Constants.EXTRA_SEQ, -1), 0, extras); + return START_NOT_STICKY; + } +} diff --git a/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestActivity.java b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestActivity.java new file mode 100644 index 000000000000..f7ea35672f0a --- /dev/null +++ b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestActivity.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.stubs.am; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.util.Log; + +import com.android.frameworks.perftests.am.util.Constants; + +public class TestActivity extends Activity { + private static final String TAG = "TestActivity"; + private static final boolean VERBOSE = InitService.VERBOSE; + + private Messenger mResultTo; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + if (VERBOSE) { + Log.i(TAG, getPackageName() + " onCreate()"); + } + setContentView(R.layout.activity_content); + mResultTo = getIntent().getParcelableExtra(Constants.EXTRA_RECEIVER_CALLBACK); + } + + @Override + public void onNewIntent(Intent intent) { + super.onNewIntent(intent); + setIntent(intent); + mResultTo = intent.getParcelableExtra(Constants.EXTRA_RECEIVER_CALLBACK); + if (intent.getBooleanExtra(Constants.EXTRA_REQ_FINISH_ACTIVITY, false)) { + if (VERBOSE) { + Log.i(TAG, getPackageName() + " finishing activity"); + } + finish(); + } + } + + @Override + protected void onResume() { + super.onResume(); + if (VERBOSE) { + Log.i(TAG, getPackageName() + " onResume()"); + } + sendResult(Constants.RESULT_NO_ERROR); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (VERBOSE) { + Log.i(TAG, getPackageName() + " onDestroy()"); + } + sendResult(Constants.RESULT_NO_ERROR); + } + + private void sendResult(int result) { + Message msg = Message.obtain(); + msg.arg1 = getIntent().getIntExtra(Constants.EXTRA_ARG1, -1); + msg.arg2 = result; + try { + mResultTo.send(msg); + } catch (RemoteException e) { + Log.e(TAG, "Error in sending result back", e); + } + } +} diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/TestFileUtils.java b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestBroadcastReceiver.java index c46175c1a977..36c7a0a3a405 100644 --- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/TestFileUtils.java +++ b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestBroadcastReceiver.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,23 +14,19 @@ * limitations under the License. */ -package com.android.server.wm.flicker; -import android.content.Context; - -import androidx.test.InstrumentationRegistry; +package com.android.stubs.am; -import com.google.common.io.ByteStreams; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; -import java.io.InputStream; +public class TestBroadcastReceiver extends BroadcastReceiver { + private static final String TAG = "TestBroadcastReceiver"; -/** - * Helper functions for test file resources. - */ -class TestFileUtils { - static byte[] readTestFile(String relativePath) throws Exception { - Context context = InstrumentationRegistry.getContext(); - InputStream in = context.getResources().getAssets().open("testdata/" + relativePath); - return ByteStreams.toByteArray(in); + @Override + public void onReceive(Context context, Intent intent) { + Log.i(TAG, context.getPackageName() + " received broadcast: " + intent); } } diff --git a/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestContentProvider.java b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestContentProvider.java new file mode 100644 index 000000000000..4fdbf1f29036 --- /dev/null +++ b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestContentProvider.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.stubs.am; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.util.Log; + +public class TestContentProvider extends ContentProvider { + private static final String TAG = "TestContentProvider"; + private static final boolean VERBOSE = InitService.VERBOSE; + + @Override + public Uri insert(Uri uri, ContentValues values) { + return null; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + return null; + } + + @Override + public String getType(Uri uri) { + return null; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + return 0; + } + + @Override + public boolean onCreate() { + if (VERBOSE) { + Log.i(TAG, getContext().getPackageName() + " onCreate()"); + } + return false; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + return 0; + } +} diff --git a/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestService.java b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestService.java new file mode 100644 index 000000000000..ba220e003203 --- /dev/null +++ b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestService.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.stubs.am; + +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.util.Log; + +import com.android.frameworks.perftests.am.util.Constants; + +public class TestService extends Service { + private static final String TAG = "TestService"; + private static final boolean VERBOSE = InitService.VERBOSE; + + private Binder mStub = new Binder(); + + @Override + public IBinder onBind(Intent intent) { + if (VERBOSE) { + Log.i(TAG, getPackageName() + " onBind()"); + } + return mStub; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (VERBOSE) { + Log.i(TAG, getPackageName() + " onStartCommand()"); + } + return START_NOT_STICKY; + } + + @Override + public boolean onUnbind(Intent intent) { + if (VERBOSE) { + Log.i(TAG, getPackageName() + " onUnbind()"); + } + Messenger messenger = intent.getParcelableExtra(Constants.EXTRA_RECEIVER_CALLBACK); + Message msg = Message.obtain(); + msg.what = Constants.MSG_UNBIND_DONE; + Bundle b = new Bundle(); + b.putString(Constants.EXTRA_SOURCE_PACKAGE, getPackageName()); + msg.obj = b; + try { + messenger.send(msg); + } catch (RemoteException e) { + Log.e(TAG, "Error in sending result back", e); + } + return false; + } +} diff --git a/tests/ActivityManagerPerfTests/tests/AndroidTest.xml b/tests/ActivityManagerPerfTests/tests/AndroidTest.xml index 76c40b2e3dc6..475bb82a9856 100644 --- a/tests/ActivityManagerPerfTests/tests/AndroidTest.xml +++ b/tests/ActivityManagerPerfTests/tests/AndroidTest.xml @@ -17,6 +17,9 @@ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> <option name="test-file-name" value="ActivityManagerPerfTests.apk"/> <option name="test-file-name" value="ActivityManagerPerfTestsTestApp.apk"/> + <option name="test-file-name" value="ActivityManagerPerfTestsStubApp1.apk"/> + <option name="test-file-name" value="ActivityManagerPerfTestsStubApp2.apk"/> + <option name="test-file-name" value="ActivityManagerPerfTestsStubApp3.apk"/> <option name="cleanup-apks" value="true"/> </target_preparer> @@ -26,4 +29,4 @@ <option name="package" value="com.android.frameworks.perftests.amtests"/> <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/> </test> -</configuration>
\ No newline at end of file +</configuration> diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/OomAdjPerfTest.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/OomAdjPerfTest.java new file mode 100644 index 000000000000..1d3ff06e6bf1 --- /dev/null +++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/OomAdjPerfTest.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.frameworks.perftests.am.tests; + + +import android.content.ContentResolver; +import android.content.Context; +import android.net.Uri; +import android.os.HandlerThread; +import android.perftests.utils.ManualBenchmarkState; +import android.perftests.utils.PerfManualStatusReporter; +import android.perftests.utils.TraceMarkParser; +import android.perftests.utils.TraceMarkParser.TraceMarkLine; +import android.perftests.utils.TraceMarkParser.TraceMarkSlice; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.LargeTest; + +import com.android.frameworks.perftests.am.util.AtraceUtils; +import com.android.frameworks.perftests.am.util.TargetPackageUtils; +import com.android.frameworks.perftests.am.util.Utils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayList; +import java.util.List; + +/** + * This benchmark test basically manipulates 3 test packages, let them bind to + * each other, send broadcast to each other, etc. All of these actions essentially + * triggers OomAdjuster to update the oom_adj scores and proc state of them. + * In the meanwhile it'll also monitor the atrace output, extract duration between + * the start and exit entries of the updateOomAdjLocked, include each of them + * into the stats; when there are enough samples in the stats, the test will + * stop and output the mean/stddev time spent on the updateOomAdjLocked. + */ +@RunWith(JUnit4.class) +@LargeTest +public final class OomAdjPerfTest { + private static final String TAG = "OomAdjPerfTest"; + private static final boolean VERBOSE = true; + + private static final String STUB_PACKAGE1_NAME = "com.android.stubs.am1"; + private static final String STUB_PACKAGE2_NAME = "com.android.stubs.am2"; + private static final String STUB_PACKAGE3_NAME = "com.android.stubs.am3"; + + private static final Uri STUB_PACKAGE1_URI = new Uri.Builder().scheme( + ContentResolver.SCHEME_CONTENT).authority("com.android.stubs.am1.testapp").build(); + private static final Uri STUB_PACKAGE2_URI = new Uri.Builder().scheme( + ContentResolver.SCHEME_CONTENT).authority("com.android.stubs.am2.testapp").build(); + private static final Uri STUB_PACKAGE3_URI = new Uri.Builder().scheme( + ContentResolver.SCHEME_CONTENT).authority("com.android.stubs.am3.testapp").build(); + private static final long NANOS_PER_MICROSECOND = 1000L; + + private static final String ATRACE_CATEGORY = "am"; + private static final String ATRACE_OOMADJ_PREFIX = "updateOomAdj_"; + + @Rule + public PerfManualStatusReporter mPerfManualStatusReporter = new PerfManualStatusReporter(); + private TraceMarkParser mTraceMarkParser = new TraceMarkParser(this::shouldFilterTraceLine); + private final ArrayList<Long> mDurations = new ArrayList<Long>(); + private Context mContext; + private HandlerThread mHandlerThread; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getTargetContext(); + mHandlerThread = new HandlerThread("command receiver"); + mHandlerThread.start(); + TargetPackageUtils.initCommandResultReceiver(mHandlerThread.getLooper()); + + Utils.runShellCommand("cmd deviceidle whitelist +" + STUB_PACKAGE1_NAME); + Utils.runShellCommand("cmd deviceidle whitelist +" + STUB_PACKAGE2_NAME); + Utils.runShellCommand("cmd deviceidle whitelist +" + STUB_PACKAGE3_NAME); + TargetPackageUtils.startStubPackage(mContext, STUB_PACKAGE1_NAME); + TargetPackageUtils.startStubPackage(mContext, STUB_PACKAGE2_NAME); + TargetPackageUtils.startStubPackage(mContext, STUB_PACKAGE3_NAME); + } + + @After + public void tearDown() { + TargetPackageUtils.stopStubPackage(mContext, STUB_PACKAGE1_NAME); + TargetPackageUtils.stopStubPackage(mContext, STUB_PACKAGE2_NAME); + TargetPackageUtils.stopStubPackage(mContext, STUB_PACKAGE3_NAME); + Utils.runShellCommand("cmd deviceidle whitelist -" + STUB_PACKAGE1_NAME); + Utils.runShellCommand("cmd deviceidle whitelist -" + STUB_PACKAGE2_NAME); + Utils.runShellCommand("cmd deviceidle whitelist -" + STUB_PACKAGE3_NAME); + mHandlerThread.quitSafely(); + } + + @Test + public void testOomAdj() { + final AtraceUtils atraceUtils = AtraceUtils.getInstance( + InstrumentationRegistry.getInstrumentation()); + final ManualBenchmarkState state = mPerfManualStatusReporter.getBenchmarkState(); + atraceUtils.startTrace(ATRACE_CATEGORY); + while (state.keepRunning(mDurations)) { + runCUJWithOomComputationOnce(); + + // Now kick off the trace dump + mDurations.clear(); + atraceUtils.performDump(mTraceMarkParser, this::handleTraceMarkSlices); + } + atraceUtils.stopTrace(); + } + + private boolean shouldFilterTraceLine(final TraceMarkLine line) { + return line.name.startsWith(ATRACE_OOMADJ_PREFIX); + } + + private void handleTraceMarkSlices(String key, List<TraceMarkSlice> slices) { + for (TraceMarkSlice slice: slices) { + mDurations.add(slice.getDurationInMicroseconds() * NANOS_PER_MICROSECOND); + } + } + + /** + * This tries to mimic a user journey, involes multiple activity/service starts/stop, + * the time spent on oom adj computation would be different between all these samples, + * but with enough samples, we'll be able to know the overall distribution of the time + * spent on it. + */ + private void runCUJWithOomComputationOnce() { + // Start activity from package1 + TargetPackageUtils.startActivity(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME); + // Start activity from package2 + TargetPackageUtils.startActivity(STUB_PACKAGE2_NAME, STUB_PACKAGE2_NAME); + // Start activity from package3 + TargetPackageUtils.startActivity(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME); + + // Stop activity in package1 + TargetPackageUtils.stopActivity(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME); + // Stop activity in package2 + TargetPackageUtils.stopActivity(STUB_PACKAGE2_NAME, STUB_PACKAGE2_NAME); + // Stop activity in package3 + TargetPackageUtils.stopActivity(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME); + + // Bind from package1 to package2 + TargetPackageUtils.bindService(STUB_PACKAGE1_NAME, STUB_PACKAGE2_NAME, + Context.BIND_AUTO_CREATE); + // Acquire content provider from package 1 to package3 + TargetPackageUtils.acquireProvider(STUB_PACKAGE1_NAME, STUB_PACKAGE3_NAME, + STUB_PACKAGE3_URI); + // Start activity from package1 + TargetPackageUtils.startActivity(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME); + // Bind from package2 to package3 + TargetPackageUtils.bindService(STUB_PACKAGE2_NAME, STUB_PACKAGE3_NAME, + Context.BIND_AUTO_CREATE); + // Unbind from package 1 to package 2 + TargetPackageUtils.unbindService(STUB_PACKAGE1_NAME, STUB_PACKAGE2_NAME, 0); + // Stop activity in package1 + TargetPackageUtils.stopActivity(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME); + + // Send broadcast to all of them + TargetPackageUtils.sendBroadcast(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME); + TargetPackageUtils.sendBroadcast(STUB_PACKAGE2_NAME, STUB_PACKAGE2_NAME); + TargetPackageUtils.sendBroadcast(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME); + + // Bind from package1 to package2 again + TargetPackageUtils.bindService(STUB_PACKAGE1_NAME, STUB_PACKAGE2_NAME, + Context.BIND_AUTO_CREATE); + // Create a cycle: package3 to package1 + TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE1_NAME, + Context.BIND_AUTO_CREATE); + + // Send broadcast to all of them again + TargetPackageUtils.sendBroadcast(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME); + TargetPackageUtils.sendBroadcast(STUB_PACKAGE2_NAME, STUB_PACKAGE2_NAME); + TargetPackageUtils.sendBroadcast(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME); + // Start activity in package3 + TargetPackageUtils.startActivity(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME); + + // Break the cycle: unbind from package3 to package1 + TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE1_NAME, 0); + + // Bind from package3 to package1 with waive priority + TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE1_NAME, + Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY); + // Release provider connection + TargetPackageUtils.releaseProvider(STUB_PACKAGE1_NAME, STUB_PACKAGE3_NAME, + STUB_PACKAGE3_URI); + // Unbind from package1 to package2 + TargetPackageUtils.unbindService(STUB_PACKAGE1_NAME, STUB_PACKAGE2_NAME, 0); + // Unbind from package2 to packagae3 + TargetPackageUtils.unbindService(STUB_PACKAGE2_NAME, STUB_PACKAGE3_NAME, 0); + + // Bind from package3 to package2 with BIND_ABOVE_CLIENT + TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, + Context.BIND_AUTO_CREATE | Context.BIND_ABOVE_CLIENT); + // Unbind from package3 to packagae2 + TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, 0); + + // Bind from package3 to package2 with BIND_ALLOW_OOM_MANAGEMENT + TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, + Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_OOM_MANAGEMENT); + // Unbind from package3 to packagae2 + TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, 0); + + // Bind from package3 to package2 with BIND_IMPORTANT + TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, + Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT); + // Unbind from package3 to packagae2 + TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, 0); + + // Bind from package3 to package2 with BIND_NOT_FOREGROUND + TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, + Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND); + // Unbind from package3 to packagae2 + TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, 0); + + // Bind from package3 to package2 with BIND_NOT_PERCEPTIBLE + TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, + Context.BIND_AUTO_CREATE | Context.BIND_NOT_PERCEPTIBLE); + // Unbind from package3 to packagae2 + TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, 0); + + // Stop activity in package3 + TargetPackageUtils.stopActivity(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME); + // Unbind from package3 to package1 + TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE1_NAME, 0); + } +} diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/AtraceUtils.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/AtraceUtils.java new file mode 100644 index 000000000000..fcccfce8bd0e --- /dev/null +++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/AtraceUtils.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.frameworks.perftests.am.util; + +import android.app.Instrumentation; +import android.app.UiAutomation; +import android.os.ParcelFileDescriptor; +import android.perftests.utils.TraceMarkParser; +import android.perftests.utils.TraceMarkParser.TraceMarkSlice; +import android.util.Log; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.List; +import java.util.function.BiConsumer; + +// Simplified version of AtraceLogger. +public class AtraceUtils { + private static final String TAG = "AtraceUtils"; + private static final boolean VERBOSE = true; + + private static final String ATRACE_START = "atrace --async_start -b %d -c %s"; + private static final String ATRACE_DUMP = "atrace --async_dump"; + private static final String ATRACE_STOP = "atrace --async_stop"; + private static final int DEFAULT_ATRACE_BUF_SIZE = 1024; + + private UiAutomation mAutomation; + private static AtraceUtils sUtils = null; + private boolean mStarted = false; + + private AtraceUtils(Instrumentation instrumentation) { + mAutomation = instrumentation.getUiAutomation(); + } + + public static AtraceUtils getInstance(Instrumentation instrumentation) { + if (sUtils == null) { + sUtils = new AtraceUtils(instrumentation); + } + return sUtils; + } + + /** + * @param categories The list of the categories to trace, separated with space. + */ + public void startTrace(String categories) { + synchronized (this) { + if (mStarted) { + throw new IllegalStateException("atrace already started"); + } + Utils.runShellCommand(String.format( + ATRACE_START, DEFAULT_ATRACE_BUF_SIZE, categories)); + mStarted = true; + } + } + + public void stopTrace() { + synchronized (this) { + mStarted = false; + Utils.runShellCommand(ATRACE_STOP); + } + } + + /** + * @param parser The function that can accept the buffer of atrace dump and parse it. + * @param handler The parse result handler + */ + public void performDump(TraceMarkParser parser, + BiConsumer<String, List<TraceMarkSlice>> handler) { + parser.reset(); + try { + if (VERBOSE) { + Log.i(TAG, "Collecting atrace dump..."); + } + writeDataToBuf(mAutomation.executeShellCommand(ATRACE_DUMP), parser); + } catch (IOException e) { + Log.e(TAG, "Error in reading dump", e); + } + parser.forAllSlices(handler); + } + + // The given file descriptor here will be closed by this function + private void writeDataToBuf(ParcelFileDescriptor pfDescriptor, + TraceMarkParser parser) throws IOException { + InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfDescriptor); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + String line; + while ((line = reader.readLine()) != null) { + parser.visit(line); + } + } + } +} diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java index 046dd6bb7dce..d7f4d9de6735 100644 --- a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java +++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java @@ -22,12 +22,18 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; -import android.os.ResultReceiver; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; import android.os.SystemClock; +import android.util.ArrayMap; +import android.util.Log; +import android.util.Pair; import org.junit.Assert; @@ -36,6 +42,7 @@ import java.util.concurrent.TimeUnit; public class TargetPackageUtils { private static final String TAG = TargetPackageUtils.class.getSimpleName(); + public static final boolean VERBOSE = true; public static final String PACKAGE_NAME = "com.android.frameworks.perftests.amteststestapp"; public static final String ACTIVITY_NAME = PACKAGE_NAME + ".TestActivity"; @@ -48,6 +55,12 @@ public class TargetPackageUtils { // Cache for test app's uid, so we only have to query it once. private static int sTestAppUid = -1; + private static final ArrayMap<String, ICommandReceiver> sStubPackages = + new ArrayMap<String, ICommandReceiver>(); + private static final ArrayMap<Integer, CountDownLatch> sCommandLatches = + new ArrayMap<Integer, CountDownLatch>(); + private static int sSeqNum = 0; + /** * Kills the test package synchronously. */ @@ -145,5 +158,160 @@ public class TargetPackageUtils { } } + private static boolean isUidRunning(int uid) { + return !Utils.runShellCommand(String.format("cmd activity get-uid-state %d", uid)) + .contains("(NONEXISTENT)"); + } + + public static void startStubPackage(Context context, String pkgName) { + stopStubPackage(context, pkgName); + try { + Pair<Integer, CountDownLatch> pair = obtainLatch(); + Intent intent = new Intent(); + intent.setComponent(new ComponentName(pkgName, Constants.STUB_INIT_SERVICE_NAME)); + intent.putExtra(Constants.EXTRA_SOURCE_PACKAGE, context.getPackageName()); + intent.putExtra(Constants.EXTRA_RECEIVER_CALLBACK, sMessenger); + intent.putExtra(Constants.EXTRA_SEQ, pair.first); + context.startService(intent); + Assert.assertTrue("Timeout when waiting for starting package " + pkgName, + pair.second.await(AWAIT_SERVICE_CONNECT_MS, TimeUnit.MILLISECONDS)); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + public static void stopStubPackage(Context context, String pkgName) { + final PackageManager pm = context.getPackageManager(); + try { + final int uid = pm.getPackageUid(pkgName, 0); + if (isUidRunning(uid)) { + ActivityManager am = context.getSystemService(ActivityManager.class); + am.forceStopPackage(pkgName); + while (isUidRunning(uid)) { + SystemClock.sleep(WAIT_TIME_MS); + } + } + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException(e); + } + } + + public static void initCommandResultReceiver(Looper looper) { + if (sMessenger == null) { + sMessenger = new Messenger(new H(looper)); + } + } + + private static void onPackageStartResult(int seq, Bundle bundle) { + ICommandReceiver receiver = ICommandReceiver.Stub.asInterface( + bundle.getBinder(Constants.EXTRA_RECEIVER_CALLBACK)); + String sourcePkg = bundle.getString(Constants.EXTRA_SOURCE_PACKAGE); + sStubPackages.put(sourcePkg, receiver); + releaseLatch(seq); + } + + private static void onCommandResult(int seq, int result) { + Assert.assertTrue("Error in command seq " + seq, result == Constants.RESULT_NO_ERROR); + releaseLatch(seq); + } + + private static Messenger sMessenger = null; + private static class H extends Handler { + H(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case Constants.REPLY_PACKAGE_START_RESULT: + onPackageStartResult(msg.arg1 /* seq */, (Bundle) msg.obj); + break; + case Constants.REPLY_COMMAND_RESULT: + onCommandResult(msg.arg1, msg.arg2); + break; + } + } + } + + private static Pair<Integer, CountDownLatch> obtainLatch() { + CountDownLatch latch = new CountDownLatch(1); + int seq; + synchronized (sCommandLatches) { + seq = sSeqNum++; + sCommandLatches.put(seq, latch); + } + return new Pair<>(seq, latch); + } + + private static void releaseLatch(int seq) { + synchronized (sCommandLatches) { + CountDownLatch latch = sCommandLatches.get(seq); + if (latch != null) { + latch.countDown(); + sCommandLatches.remove(seq); + } + } + } + + public static void sendCommand(int command, String sourcePackage, String targetPackage, + int flags, Bundle bundle, boolean waitForResult) { + ICommandReceiver receiver = sStubPackages.get(sourcePackage); + Assert.assertTrue("Package hasn't been started: " + sourcePackage, receiver != null); + try { + Pair<Integer, CountDownLatch> pair = null; + if (waitForResult) { + pair = obtainLatch(); + } + if (VERBOSE) { + Log.i(TAG, "Sending command=" + command + ", seq=" + pair.first + ", from=" + + sourcePackage + ", to=" + targetPackage + ", flags=" + flags); + } + receiver.sendCommand(command, pair.first, sourcePackage, targetPackage, flags, bundle); + if (waitForResult) { + Assert.assertTrue("Timeout when waiting for command " + command + " from " + + sourcePackage + " to " + targetPackage, + pair.second.await(AWAIT_SERVICE_CONNECT_MS, TimeUnit.MILLISECONDS)); + } + } catch (RemoteException | InterruptedException e) { + throw new RuntimeException(e); + } + } + + public static void bindService(String sourcePackage, String targetPackage, int flags) { + sendCommand(Constants.COMMAND_BIND_SERVICE, sourcePackage, targetPackage, flags, null, + true); + } + + public static void unbindService(String sourcePackage, String targetPackage, int flags) { + sendCommand(Constants.COMMAND_UNBIND_SERVICE, sourcePackage, targetPackage, flags, null, + true); + } + + public static void acquireProvider(String sourcePackage, String targetPackage, Uri uri) { + Bundle bundle = new Bundle(); + bundle.putParcelable(Constants.EXTRA_URI, uri); + sendCommand(Constants.COMMAND_ACQUIRE_CONTENT_PROVIDER, sourcePackage, targetPackage, 0, + bundle, true); + } + + public static void releaseProvider(String sourcePackage, String targetPackage, Uri uri) { + Bundle bundle = new Bundle(); + bundle.putParcelable(Constants.EXTRA_URI, uri); + sendCommand(Constants.COMMAND_RELEASE_CONTENT_PROVIDER, sourcePackage, targetPackage, 0, + bundle, true); + } + + public static void sendBroadcast(String sourcePackage, String targetPackage) { + sendCommand(Constants.COMMAND_SEND_BROADCAST, sourcePackage, targetPackage, 0, null, true); + } + + public static void startActivity(String sourcePackage, String targetPackage) { + sendCommand(Constants.COMMAND_START_ACTIVITY, sourcePackage, targetPackage, 0, null, true); + } + + public static void stopActivity(String sourcePackage, String targetPackage) { + sendCommand(Constants.COMMAND_STOP_ACTIVITY, sourcePackage, targetPackage, 0, null, true); + } } diff --git a/tests/ActivityManagerPerfTests/utils/Android.bp b/tests/ActivityManagerPerfTests/utils/Android.bp index 300b7ea998fa..766c3acf3c09 100644 --- a/tests/ActivityManagerPerfTests/utils/Android.bp +++ b/tests/ActivityManagerPerfTests/utils/Android.bp @@ -18,6 +18,7 @@ java_test { srcs: [ "src/**/*.java", "src/com/android/frameworks/perftests/am/util/ITimeReceiverCallback.aidl", + "src/com/android/frameworks/perftests/am/util/ICommandReceiver.aidl", ], static_libs: [ "androidx.test.rules", diff --git a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java index 9b076c507ff8..8e58665f5352 100644 --- a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java +++ b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java @@ -30,4 +30,33 @@ public class Constants { public static final String EXTRA_RECEIVER_CALLBACK = "receiver_callback_binder"; public static final String EXTRA_LOOPER_IDLE_CALLBACK = "looper_idle_callback_binder"; + public static final String EXTRA_SOURCE_PACKAGE = "source_package"; + public static final String EXTRA_URI = "uri"; + public static final String EXTRA_REQ_FINISH_ACTIVITY = "req_finish_activity"; + public static final String EXTRA_SEQ = "seq"; + public static final String EXTRA_ARG1 = "arg1"; + public static final String EXTRA_ARG2 = "arg2"; + + public static final int RESULT_NO_ERROR = 0; + public static final int RESULT_ERROR = 1; + public static final String STUB_INIT_SERVICE_NAME = "com.android.stubs.am.InitService"; + + public static final int COMMAND_BIND_SERVICE = 1; + public static final int COMMAND_UNBIND_SERVICE = 2; + public static final int COMMAND_ACQUIRE_CONTENT_PROVIDER = 3; + public static final int COMMAND_RELEASE_CONTENT_PROVIDER = 4; + public static final int COMMAND_SEND_BROADCAST = 5; + public static final int COMMAND_START_ACTIVITY = 6; + public static final int COMMAND_STOP_ACTIVITY = 7; + + public static final int MSG_DEFAULT = 0; + public static final int MSG_UNBIND_DONE = 1; + + public static final int REPLY_PACKAGE_START_RESULT = 0; + public static final int REPLY_COMMAND_RESULT = 1; + + public static final String STUB_ACTION_ACTIVITY = + "com.android.stubs.am.ACTION_START_TEST_ACTIVITY"; + public static final String STUB_ACTION_BROADCAST = + "com.android.stubs.am.ACTION_BROADCAST_TEST"; } diff --git a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/ICommandReceiver.aidl b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/ICommandReceiver.aidl new file mode 100644 index 000000000000..59ea7616a59e --- /dev/null +++ b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/ICommandReceiver.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.frameworks.perftests.am.util; + +import android.os.Bundle; + +interface ICommandReceiver { + oneway void sendCommand(int command, int seq, String sourcePackage, String targetPackage, + int flags, in Bundle bundle); +} diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java index 07b3a97817a3..768cfaea4898 100644 --- a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java +++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java @@ -32,6 +32,7 @@ import android.content.pm.UserInfo; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -587,7 +588,7 @@ public class ActivityTestMain extends Activity { desc.setLabel("Added #" + i); Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon); if ((i&1) == 0) { - desc.setIcon(bitmap); + desc.setIcon(Icon.createWithBitmap(bitmap)); } int taskId = am.addAppTask(this, intent, desc, bitmap); Log.i(TAG, "Added new task id #" + taskId); diff --git a/tests/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp new file mode 100644 index 000000000000..02c75edafcd5 --- /dev/null +++ b/tests/ApkVerityTest/Android.bp @@ -0,0 +1,34 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +java_test_host { + name: "ApkVerityTest", + srcs: ["src/**/*.java"], + libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"], + test_suites: ["general-tests", "vts"], + target_required: [ + "block_device_writer_module", + ], + data: [ + ":ApkVerityTestCertDer", + ":ApkVerityTestApp", + ":ApkVerityTestAppFsvSig", + ":ApkVerityTestAppDm", + ":ApkVerityTestAppDmFsvSig", + ":ApkVerityTestAppSplit", + ":ApkVerityTestAppSplitFsvSig", + ":ApkVerityTestAppSplitDm", + ":ApkVerityTestAppSplitDmFsvSig", + ], +} diff --git a/tests/ApkVerityTest/AndroidTest.xml b/tests/ApkVerityTest/AndroidTest.xml new file mode 100644 index 000000000000..55704eda905e --- /dev/null +++ b/tests/ApkVerityTest/AndroidTest.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="APK fs-verity integration/regression test"> + <option name="test-suite-tag" value="apct" /> + + <!-- This test requires root to write against block device. --> + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" /> + + <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> + <!-- Disable package verifier prevents it holding the target APK's fd that prevents cache + eviction. --> + <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" /> + <option name="restore-settings" value="true" /> + + <!-- Skip in order to prevent reboot that confuses the test flow. --> + <option name="force-skip-system-props" value="true" /> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" /> + <option name="push" value="ApkVerityTestCert.der->/data/local/tmp/ApkVerityTestCert.der" /> + </target_preparer> + + <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" > + <option name="jar" value="ApkVerityTest.jar" /> + </test> +</configuration> diff --git a/tests/ApkVerityTest/ApkVerityTestApp/Android.bp b/tests/ApkVerityTest/ApkVerityTestApp/Android.bp new file mode 100644 index 000000000000..69632b215822 --- /dev/null +++ b/tests/ApkVerityTest/ApkVerityTestApp/Android.bp @@ -0,0 +1,29 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_test_helper_app { + name: "ApkVerityTestApp", + manifest: "AndroidManifest.xml", + srcs: ["src/**/*.java"], +} + +android_test_helper_app { + name: "ApkVerityTestAppSplit", + manifest: "feature_split/AndroidManifest.xml", + srcs: ["src/**/*.java"], + aaptflags: [ + "--custom-package com.android.apkverity.feature_x", + "--package-id 0x80", + ], +} diff --git a/tests/ApkVerityTest/ApkVerityTestApp/AndroidManifest.xml b/tests/ApkVerityTest/ApkVerityTestApp/AndroidManifest.xml new file mode 100644 index 000000000000..0b3ff77c2cdf --- /dev/null +++ b/tests/ApkVerityTest/ApkVerityTestApp/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.apkverity"> + <application> + <activity android:name=".DummyActivity"/> + </application> +</manifest> diff --git a/tests/ApkVerityTest/ApkVerityTestApp/feature_split/AndroidManifest.xml b/tests/ApkVerityTest/ApkVerityTestApp/feature_split/AndroidManifest.xml new file mode 100644 index 000000000000..3f1a4f3a26a1 --- /dev/null +++ b/tests/ApkVerityTest/ApkVerityTestApp/feature_split/AndroidManifest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.apkverity" + android:isFeatureSplit="true" + split="feature_x"> + <application> + <activity android:name=".feature_x.DummyActivity"/> + </application> +</manifest> diff --git a/tests/ApkVerityTest/ApkVerityTestApp/feature_split/src/com/android/apkverity/feature_x/DummyActivity.java b/tests/ApkVerityTest/ApkVerityTestApp/feature_split/src/com/android/apkverity/feature_x/DummyActivity.java new file mode 100644 index 000000000000..0f694c293330 --- /dev/null +++ b/tests/ApkVerityTest/ApkVerityTestApp/feature_split/src/com/android/apkverity/feature_x/DummyActivity.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.apkverity.feature_x; + +import android.app.Activity; + +/** Dummy class just to generate some dex */ +public class DummyActivity extends Activity {} diff --git a/tests/ApkVerityTest/ApkVerityTestApp/src/com/android/apkverity/DummyActivity.java b/tests/ApkVerityTest/ApkVerityTestApp/src/com/android/apkverity/DummyActivity.java new file mode 100644 index 000000000000..837c7be37504 --- /dev/null +++ b/tests/ApkVerityTest/ApkVerityTestApp/src/com/android/apkverity/DummyActivity.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.apkverity; + +import android.app.Activity; + +/** Dummy class just to generate some dex */ +public class DummyActivity extends Activity {} diff --git a/tests/ApkVerityTest/TEST_MAPPING b/tests/ApkVerityTest/TEST_MAPPING new file mode 100644 index 000000000000..72d96148c69f --- /dev/null +++ b/tests/ApkVerityTest/TEST_MAPPING @@ -0,0 +1,12 @@ +{ + "presubmit": [ + { + "name": "ApkVerityTest" + }, + // nextgen test only runs during postsubmit. + { + "name": "ApkVerityTest", + "keywords": ["nextgen"] + } + ] +} diff --git a/tests/ApkVerityTest/block_device_writer/Android.bp b/tests/ApkVerityTest/block_device_writer/Android.bp new file mode 100644 index 000000000000..37fbc29470f6 --- /dev/null +++ b/tests/ApkVerityTest/block_device_writer/Android.bp @@ -0,0 +1,53 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This is a cc_test just because it supports test_suites. This should be converted to something +// like cc_binary_test_helper once supported, thus auto_gen_config:false below. +cc_test { + // Depending on how the test runs, the executable may be uploaded to different location. + // Before the bug in the file pusher is fixed, workaround by making the name unique. + // See b/124718249#comment12. + name: "block_device_writer_module", + stem: "block_device_writer", + + srcs: ["block_device_writer.cpp"], + cflags: [ + "-D_FILE_OFFSET_BITS=64", + "-Wall", + "-Werror", + "-Wextra", + "-g", + ], + shared_libs: ["libbase", "libutils"], + // For some reasons, cuttlefish (x86) uses x86_64 test suites for testing. Unfortunately, when + // the uploader does not pick up the executable from correct output location. The following + // workaround allows the test to: + // * upload the 32-bit exectuable for both 32 and 64 bits devices to use + // * refer to the same executable name in Java + // * no need to force the Java test to be archiecture specific. + // + // See b/145573317 for details. + multilib: { + lib32: { + suffix: "", + }, + lib64: { + suffix: "64", // not really used + }, + }, + + auto_gen_config: false, + test_suites: ["general-tests", "pts", "vts"], + gtest: false, +} diff --git a/tests/ApkVerityTest/block_device_writer/block_device_writer.cpp b/tests/ApkVerityTest/block_device_writer/block_device_writer.cpp new file mode 100644 index 000000000000..02dfd732a716 --- /dev/null +++ b/tests/ApkVerityTest/block_device_writer/block_device_writer.cpp @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include <cassert> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <memory> + +#include <errno.h> +#include <fcntl.h> +#include <linux/fiemap.h> +#include <linux/fs.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <android-base/unique_fd.h> + +// This program modifies a file at given offset, but directly against the block +// device, purposely to bypass the filesystem. Note that the change on block +// device may not reflect the same way when read from filesystem, for example, +// when the file is encrypted on disk. +// +// Only one byte is supported for now just so that we don't need to handle the +// case when the range crosses different "extents". +// +// References: +// https://www.kernel.org/doc/Documentation/filesystems/fiemap.txt +// https://git.kernel.org/pub/scm/fs/xfs/xfsprogs-dev.git/tree/io/fiemap.c + +#ifndef F2FS_IOC_SET_PIN_FILE +#ifndef F2FS_IOCTL_MAGIC +#define F2FS_IOCTL_MAGIC 0xf5 +#endif +#define F2FS_IOC_SET_PIN_FILE _IOW(F2FS_IOCTL_MAGIC, 13, __u32) +#define F2FS_IOC_GET_PIN_FILE _IOR(F2FS_IOCTL_MAGIC, 14, __u32) +#endif + +struct Args { + const char* block_device; + const char* file_name; + uint64_t byte_offset; + bool use_f2fs_pinning; +}; + +class ScopedF2fsFilePinning { + public: + explicit ScopedF2fsFilePinning(const char* file_path) { + fd_.reset(TEMP_FAILURE_RETRY(open(file_path, O_WRONLY | O_CLOEXEC, 0))); + if (fd_.get() == -1) { + perror("Failed to open"); + return; + } + __u32 set = 1; + ioctl(fd_.get(), F2FS_IOC_SET_PIN_FILE, &set); + } + + ~ScopedF2fsFilePinning() { + __u32 set = 0; + ioctl(fd_.get(), F2FS_IOC_SET_PIN_FILE, &set); + } + + private: + android::base::unique_fd fd_; +}; + +ssize_t get_logical_block_size(const char* block_device) { + android::base::unique_fd fd(open(block_device, O_RDONLY)); + if (fd.get() < 0) { + fprintf(stderr, "open %s failed\n", block_device); + return -1; + } + + int size; + if (ioctl(fd, BLKSSZGET, &size) < 0) { + fprintf(stderr, "ioctl(BLKSSZGET) failed: %s\n", strerror(errno)); + return -1; + } + return size; +} + +int64_t get_physical_offset(const char* file_name, uint64_t byte_offset) { + android::base::unique_fd fd(open(file_name, O_RDONLY)); + if (fd.get() < 0) { + fprintf(stderr, "open %s failed\n", file_name); + return -1; + } + + const int map_size = sizeof(struct fiemap) + sizeof(struct fiemap_extent); + char fiemap_buffer[map_size] = {0}; + struct fiemap* fiemap = reinterpret_cast<struct fiemap*>(&fiemap_buffer); + + fiemap->fm_flags = FIEMAP_FLAG_SYNC; + fiemap->fm_start = byte_offset; + fiemap->fm_length = 1; + fiemap->fm_extent_count = 1; + + int ret = ioctl(fd.get(), FS_IOC_FIEMAP, fiemap); + if (ret < 0) { + fprintf(stderr, "ioctl(FS_IOC_FIEMAP) failed: %s\n", strerror(errno)); + return -1; + } + + if (fiemap->fm_mapped_extents != 1) { + fprintf(stderr, "fm_mapped_extents != 1 (is %d)\n", + fiemap->fm_mapped_extents); + return -1; + } + + struct fiemap_extent* extent = &fiemap->fm_extents[0]; + printf( + "logical offset: %llu, physical offset: %llu, length: %llu, " + "flags: %x\n", + extent->fe_logical, extent->fe_physical, extent->fe_length, + extent->fe_flags); + if (extent->fe_flags & (FIEMAP_EXTENT_UNKNOWN | + FIEMAP_EXTENT_UNWRITTEN)) { + fprintf(stderr, "Failed to locate physical offset safely\n"); + return -1; + } + + return extent->fe_physical + (byte_offset - extent->fe_logical); +} + +int read_block_from_device(const char* device_path, uint64_t block_offset, + ssize_t block_size, char* block_buffer) { + assert(block_offset % block_size == 0); + android::base::unique_fd fd(open(device_path, O_RDONLY | O_DIRECT)); + if (fd.get() < 0) { + fprintf(stderr, "open %s failed\n", device_path); + return -1; + } + + ssize_t retval = + TEMP_FAILURE_RETRY(pread(fd, block_buffer, block_size, block_offset)); + if (retval != block_size) { + fprintf(stderr, "read returns error or incomplete result (%zu): %s\n", + retval, strerror(errno)); + return -1; + } + return 0; +} + +int write_block_to_device(const char* device_path, uint64_t block_offset, + ssize_t block_size, char* block_buffer) { + assert(block_offset % block_size == 0); + android::base::unique_fd fd(open(device_path, O_WRONLY | O_DIRECT)); + if (fd.get() < 0) { + fprintf(stderr, "open %s failed\n", device_path); + return -1; + } + + ssize_t retval = TEMP_FAILURE_RETRY( + pwrite(fd.get(), block_buffer, block_size, block_offset)); + if (retval != block_size) { + fprintf(stderr, "write returns error or incomplete result (%zu): %s\n", + retval, strerror(errno)); + return -1; + } + return 0; +} + +std::unique_ptr<Args> parse_args(int argc, const char** argv) { + if (argc != 4 && argc != 5) { + fprintf(stderr, + "Usage: %s [--use-f2fs-pinning] block_dev filename byte_offset\n" + "\n" + "This program bypasses filesystem and damages the specified byte\n" + "at the physical position on <block_dev> corresponding to the\n" + "logical byte location in <filename>.\n", + argv[0]); + return nullptr; + } + + auto args = std::make_unique<Args>(); + const char** arg = &argv[1]; + args->use_f2fs_pinning = strcmp(*arg, "--use-f2fs-pinning") == 0; + if (args->use_f2fs_pinning) { + ++arg; + } + args->block_device = *(arg++); + args->file_name = *(arg++); + args->byte_offset = strtoull(*arg, nullptr, 10); + if (args->byte_offset == ULLONG_MAX) { + perror("Invalid byte offset"); + return nullptr; + } + return args; +} + +int main(int argc, const char** argv) { + std::unique_ptr<Args> args = parse_args(argc, argv); + if (args == nullptr) { + return -1; + } + + ssize_t block_size = get_logical_block_size(args->block_device); + if (block_size < 0) { + return -1; + } + + std::unique_ptr<ScopedF2fsFilePinning> pinned_file; + if (args->use_f2fs_pinning) { + pinned_file = std::make_unique<ScopedF2fsFilePinning>(args->file_name); + } + + int64_t physical_offset_signed = get_physical_offset(args->file_name, args->byte_offset); + if (physical_offset_signed < 0) { + return -1; + } + + uint64_t physical_offset = static_cast<uint64_t>(physical_offset_signed); + uint64_t offset_within_block = physical_offset % block_size; + uint64_t physical_block_offset = physical_offset - offset_within_block; + + // Direct I/O requires aligned buffer + std::unique_ptr<char> buf(static_cast<char*>( + aligned_alloc(block_size /* alignment */, block_size /* size */))); + + if (read_block_from_device(args->block_device, physical_block_offset, block_size, + buf.get()) < 0) { + return -1; + } + char* p = buf.get() + offset_within_block; + printf("before: %hhx\n", *p); + *p ^= 0xff; + printf("after: %hhx\n", *p); + if (write_block_to_device(args->block_device, physical_block_offset, block_size, + buf.get()) < 0) { + return -1; + } + + return 0; +} diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java new file mode 100644 index 000000000000..629b6c714ae8 --- /dev/null +++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java @@ -0,0 +1,506 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.apkverity; + +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.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; + +import android.platform.test.annotations.RootPermissionTest; + +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; +import com.android.tradefed.util.CommandResult; +import com.android.tradefed.util.CommandStatus; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; + +/** + * This test makes sure app installs with fs-verity signature, and on-access verification works. + * + * <p>When an app is installed, all or none of the files should have their corresponding .fsv_sig + * signature file. Otherwise, install will fail. + * + * <p>Once installed, file protected by fs-verity is verified by kernel every time a block is loaded + * from disk to memory. The file is immutable by design, enforced by filesystem. + * + * <p>In order to make sure a block of the file is readable only if the underlying block on disk + * stay intact, the test needs to bypass the filesystem and tampers with the corresponding physical + * address against the block device. + * + * <p>Requirements to run this test: + * <ul> + * <li>Device is rootable</li> + * <li>The filesystem supports fs-verity</li> + * <li>The feature flag is enabled</li> + * </ul> + */ +@RootPermissionTest +@RunWith(DeviceJUnit4ClassRunner.class) +public class ApkVerityTest extends BaseHostJUnit4Test { + private static final String TARGET_PACKAGE = "com.android.apkverity"; + + private static final String BASE_APK = "ApkVerityTestApp.apk"; + private static final String BASE_APK_DM = "ApkVerityTestApp.dm"; + private static final String SPLIT_APK = "ApkVerityTestAppSplit.apk"; + private static final String SPLIT_APK_DM = "ApkVerityTestAppSplit.dm"; + + private static final String INSTALLED_BASE_APK = "base.apk"; + private static final String INSTALLED_BASE_DM = "base.dm"; + private static final String INSTALLED_SPLIT_APK = "split_feature_x.apk"; + private static final String INSTALLED_SPLIT_DM = "split_feature_x.dm"; + private static final String INSTALLED_BASE_APK_FSV_SIG = "base.apk.fsv_sig"; + private static final String INSTALLED_BASE_DM_FSV_SIG = "base.dm.fsv_sig"; + private static final String INSTALLED_SPLIT_APK_FSV_SIG = "split_feature_x.apk.fsv_sig"; + private static final String INSTALLED_SPLIT_DM_FSV_SIG = "split_feature_x.dm.fsv_sig"; + + private static final String DAMAGING_EXECUTABLE = "/data/local/tmp/block_device_writer"; + private static final String CERT_PATH = "/data/local/tmp/ApkVerityTestCert.der"; + + private static final String APK_VERITY_STANDARD_MODE = "2"; + + /** Only 4K page is supported by fs-verity currently. */ + private static final int FSVERITY_PAGE_SIZE = 4096; + + private ITestDevice mDevice; + private String mKeyId; + + @Before + public void setUp() throws DeviceNotAvailableException { + mDevice = getDevice(); + + String apkVerityMode = mDevice.getProperty("ro.apk_verity.mode"); + assumeTrue(mDevice.getLaunchApiLevel() >= 30 + || APK_VERITY_STANDARD_MODE.equals(apkVerityMode)); + + mKeyId = expectRemoteCommandToSucceed( + "mini-keyctl padd asymmetric fsv_test .fs-verity < " + CERT_PATH).trim(); + if (!mKeyId.matches("^\\d+$")) { + String keyId = mKeyId; + mKeyId = null; + fail("Key ID is not decimal: " + keyId); + } + + uninstallPackage(TARGET_PACKAGE); + } + + @After + public void tearDown() throws DeviceNotAvailableException { + uninstallPackage(TARGET_PACKAGE); + + if (mKeyId != null) { + expectRemoteCommandToSucceed("mini-keyctl unlink " + mKeyId + " .fs-verity"); + } + } + + @Test + public void testFsverityKernelSupports() throws DeviceNotAvailableException { + ITestDevice.MountPointInfo mountPoint = mDevice.getMountPointInfo("/data"); + expectRemoteCommandToSucceed("test -f /sys/fs/" + mountPoint.type + "/features/verity"); + } + + @Test + public void testInstallBase() throws DeviceNotAvailableException, FileNotFoundException { + new InstallMultiple() + .addFileAndSignature(BASE_APK) + .run(); + assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE)); + + verifyInstalledFiles( + INSTALLED_BASE_APK, + INSTALLED_BASE_APK_FSV_SIG); + verifyInstalledFilesHaveFsverity(); + } + + @Test + public void testInstallBaseWithWrongSignature() + throws DeviceNotAvailableException, FileNotFoundException { + new InstallMultiple() + .addFile(BASE_APK) + .addFile(SPLIT_APK_DM + ".fsv_sig", + BASE_APK + ".fsv_sig") + .runExpectingFailure(); + } + + @Test + public void testInstallBaseWithSplit() + throws DeviceNotAvailableException, FileNotFoundException { + new InstallMultiple() + .addFileAndSignature(BASE_APK) + .addFileAndSignature(SPLIT_APK) + .run(); + assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE)); + + verifyInstalledFiles( + INSTALLED_BASE_APK, + INSTALLED_BASE_APK_FSV_SIG, + INSTALLED_SPLIT_APK, + INSTALLED_SPLIT_APK_FSV_SIG); + verifyInstalledFilesHaveFsverity(); + } + + @Test + public void testInstallBaseWithDm() throws DeviceNotAvailableException, FileNotFoundException { + new InstallMultiple() + .addFileAndSignature(BASE_APK) + .addFileAndSignature(BASE_APK_DM) + .run(); + assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE)); + + verifyInstalledFiles( + INSTALLED_BASE_APK, + INSTALLED_BASE_APK_FSV_SIG, + INSTALLED_BASE_DM, + INSTALLED_BASE_DM_FSV_SIG); + verifyInstalledFilesHaveFsverity(); + } + + @Test + public void testInstallEverything() throws DeviceNotAvailableException, FileNotFoundException { + new InstallMultiple() + .addFileAndSignature(BASE_APK) + .addFileAndSignature(BASE_APK_DM) + .addFileAndSignature(SPLIT_APK) + .addFileAndSignature(SPLIT_APK_DM) + .run(); + assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE)); + + verifyInstalledFiles( + INSTALLED_BASE_APK, + INSTALLED_BASE_APK_FSV_SIG, + INSTALLED_BASE_DM, + INSTALLED_BASE_DM_FSV_SIG, + INSTALLED_SPLIT_APK, + INSTALLED_SPLIT_APK_FSV_SIG, + INSTALLED_SPLIT_DM, + INSTALLED_SPLIT_DM_FSV_SIG); + verifyInstalledFilesHaveFsverity(); + } + + @Test + public void testInstallSplitOnly() + throws DeviceNotAvailableException, FileNotFoundException { + new InstallMultiple() + .addFileAndSignature(BASE_APK) + .run(); + assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE)); + verifyInstalledFiles( + INSTALLED_BASE_APK, + INSTALLED_BASE_APK_FSV_SIG); + + new InstallMultiple() + .inheritFrom(TARGET_PACKAGE) + .addFileAndSignature(SPLIT_APK) + .run(); + + verifyInstalledFiles( + INSTALLED_BASE_APK, + INSTALLED_BASE_APK_FSV_SIG, + INSTALLED_SPLIT_APK, + INSTALLED_SPLIT_APK_FSV_SIG); + verifyInstalledFilesHaveFsverity(); + } + + @Test + public void testInstallSplitOnlyMissingSignature() + throws DeviceNotAvailableException, FileNotFoundException { + new InstallMultiple() + .addFileAndSignature(BASE_APK) + .run(); + assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE)); + verifyInstalledFiles( + INSTALLED_BASE_APK, + INSTALLED_BASE_APK_FSV_SIG); + + new InstallMultiple() + .inheritFrom(TARGET_PACKAGE) + .addFile(SPLIT_APK) + .runExpectingFailure(); + } + + @Test + public void testInstallSplitOnlyWithoutBaseSignature() + throws DeviceNotAvailableException, FileNotFoundException { + new InstallMultiple() + .addFile(BASE_APK) + .run(); + assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE)); + verifyInstalledFiles(INSTALLED_BASE_APK); + + new InstallMultiple() + .inheritFrom(TARGET_PACKAGE) + .addFileAndSignature(SPLIT_APK) + .run(); + verifyInstalledFiles( + INSTALLED_BASE_APK, + INSTALLED_SPLIT_APK, + INSTALLED_SPLIT_APK_FSV_SIG); + + } + + @Test + public void testInstallOnlyBaseHasFsvSig() + throws DeviceNotAvailableException, FileNotFoundException { + new InstallMultiple() + .addFileAndSignature(BASE_APK) + .addFile(BASE_APK_DM) + .addFile(SPLIT_APK) + .addFile(SPLIT_APK_DM) + .runExpectingFailure(); + } + + @Test + public void testInstallOnlyDmHasFsvSig() + throws DeviceNotAvailableException, FileNotFoundException { + new InstallMultiple() + .addFile(BASE_APK) + .addFileAndSignature(BASE_APK_DM) + .addFile(SPLIT_APK) + .addFile(SPLIT_APK_DM) + .runExpectingFailure(); + } + + @Test + public void testInstallOnlySplitHasFsvSig() + throws DeviceNotAvailableException, FileNotFoundException { + new InstallMultiple() + .addFile(BASE_APK) + .addFile(BASE_APK_DM) + .addFileAndSignature(SPLIT_APK) + .addFile(SPLIT_APK_DM) + .runExpectingFailure(); + } + + @Test + public void testInstallBaseWithFsvSigThenSplitWithout() + throws DeviceNotAvailableException, FileNotFoundException { + new InstallMultiple() + .addFileAndSignature(BASE_APK) + .run(); + assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE)); + verifyInstalledFiles( + INSTALLED_BASE_APK, + INSTALLED_BASE_APK_FSV_SIG); + + new InstallMultiple() + .addFile(SPLIT_APK) + .runExpectingFailure(); + } + + @Test + public void testInstallBaseWithoutFsvSigThenSplitWith() + throws DeviceNotAvailableException, FileNotFoundException { + new InstallMultiple() + .addFile(BASE_APK) + .run(); + assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE)); + verifyInstalledFiles(INSTALLED_BASE_APK); + + new InstallMultiple() + .addFileAndSignature(SPLIT_APK) + .runExpectingFailure(); + } + + @Test + public void testFsverityFileIsImmutableAndReadable() throws DeviceNotAvailableException { + new InstallMultiple().addFileAndSignature(BASE_APK).run(); + String apkPath = getApkPath(TARGET_PACKAGE); + + assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE)); + expectRemoteCommandToFail("echo -n '' >> " + apkPath); + expectRemoteCommandToSucceed("cat " + apkPath + " > /dev/null"); + } + + @Test + public void testFsverityFailToReadModifiedBlockAtFront() throws DeviceNotAvailableException { + new InstallMultiple().addFileAndSignature(BASE_APK).run(); + String apkPath = getApkPath(TARGET_PACKAGE); + + long apkSize = getFileSizeInBytes(apkPath); + long offsetFirstByte = 0; + + // The first two pages should be both readable at first. + assertTrue(canReadByte(apkPath, offsetFirstByte)); + if (apkSize > offsetFirstByte + FSVERITY_PAGE_SIZE) { + assertTrue(canReadByte(apkPath, offsetFirstByte + FSVERITY_PAGE_SIZE)); + } + + // Damage the file directly against the block device. + damageFileAgainstBlockDevice(apkPath, offsetFirstByte); + + // Expect actual read from disk to fail but only at damaged page. + dropCaches(); + assertFalse(canReadByte(apkPath, offsetFirstByte)); + if (apkSize > offsetFirstByte + FSVERITY_PAGE_SIZE) { + long lastByteOfTheSamePage = + offsetFirstByte % FSVERITY_PAGE_SIZE + FSVERITY_PAGE_SIZE - 1; + assertFalse(canReadByte(apkPath, lastByteOfTheSamePage)); + assertTrue(canReadByte(apkPath, lastByteOfTheSamePage + 1)); + } + } + + @Test + public void testFsverityFailToReadModifiedBlockAtBack() throws DeviceNotAvailableException { + new InstallMultiple().addFileAndSignature(BASE_APK).run(); + String apkPath = getApkPath(TARGET_PACKAGE); + + long apkSize = getFileSizeInBytes(apkPath); + long offsetOfLastByte = apkSize - 1; + + // The first two pages should be both readable at first. + assertTrue(canReadByte(apkPath, offsetOfLastByte)); + if (offsetOfLastByte - FSVERITY_PAGE_SIZE > 0) { + assertTrue(canReadByte(apkPath, offsetOfLastByte - FSVERITY_PAGE_SIZE)); + } + + // Damage the file directly against the block device. + damageFileAgainstBlockDevice(apkPath, offsetOfLastByte); + + // Expect actual read from disk to fail but only at damaged page. + dropCaches(); + assertFalse(canReadByte(apkPath, offsetOfLastByte)); + if (offsetOfLastByte - FSVERITY_PAGE_SIZE > 0) { + long firstByteOfTheSamePage = offsetOfLastByte - offsetOfLastByte % FSVERITY_PAGE_SIZE; + assertFalse(canReadByte(apkPath, firstByteOfTheSamePage)); + assertTrue(canReadByte(apkPath, firstByteOfTheSamePage - 1)); + } + } + + private void verifyInstalledFilesHaveFsverity() throws DeviceNotAvailableException { + // Verify that all files are protected by fs-verity + String apkPath = getApkPath(TARGET_PACKAGE); + String appDir = apkPath.substring(0, apkPath.lastIndexOf("/")); + long kTargetOffset = 0; + for (String basename : expectRemoteCommandToSucceed("ls " + appDir).split("\n")) { + if (basename.endsWith(".apk") || basename.endsWith(".dm")) { + String path = appDir + "/" + basename; + damageFileAgainstBlockDevice(path, kTargetOffset); + + // Retry is sometimes needed to pass the test. Package manager may have FD leaks + // (see b/122744005 as example) that prevents the file in question to be evicted + // from filesystem cache. Forcing GC workarounds the problem. + int retry = 5; + for (; retry > 0; retry--) { + dropCaches(); + if (!canReadByte(path, kTargetOffset)) { + break; + } + try { + CLog.d("lsof: " + expectRemoteCommandToSucceed("lsof " + apkPath)); + Thread.sleep(1000); + String pid = expectRemoteCommandToSucceed("pidof system_server"); + mDevice.executeShellV2Command("kill -10 " + pid); // force GC + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + } + assertTrue("Read from " + path + " should fail", retry > 0); + } + } + } + + private void verifyInstalledFiles(String... filenames) throws DeviceNotAvailableException { + String apkPath = getApkPath(TARGET_PACKAGE); + String appDir = apkPath.substring(0, apkPath.lastIndexOf("/")); + // Exclude directories since we only care about files. + HashSet<String> actualFiles = new HashSet<>(Arrays.asList( + expectRemoteCommandToSucceed("ls -p " + appDir + " | grep -v '/'").split("\n"))); + + HashSet<String> expectedFiles = new HashSet<>(Arrays.asList(filenames)); + assertEquals(expectedFiles, actualFiles); + } + + private void damageFileAgainstBlockDevice(String path, long offsetOfTargetingByte) + throws DeviceNotAvailableException { + assertTrue(path.startsWith("/data/")); + ITestDevice.MountPointInfo mountPoint = mDevice.getMountPointInfo("/data"); + ArrayList<String> args = new ArrayList<>(); + args.add(DAMAGING_EXECUTABLE); + if ("f2fs".equals(mountPoint.type)) { + args.add("--use-f2fs-pinning"); + } + args.add(mountPoint.filesystem); + args.add(path); + args.add(Long.toString(offsetOfTargetingByte)); + expectRemoteCommandToSucceed(String.join(" ", args)); + } + + private String getApkPath(String packageName) throws DeviceNotAvailableException { + String line = expectRemoteCommandToSucceed("pm path " + packageName + " | grep base.apk"); + int index = line.trim().indexOf(":"); + assertTrue(index >= 0); + return line.substring(index + 1); + } + + private long getFileSizeInBytes(String packageName) throws DeviceNotAvailableException { + return Long.parseLong(expectRemoteCommandToSucceed("stat -c '%s' " + packageName).trim()); + } + + private void dropCaches() throws DeviceNotAvailableException { + expectRemoteCommandToSucceed("sync && echo 1 > /proc/sys/vm/drop_caches"); + } + + private boolean canReadByte(String filePath, long offset) throws DeviceNotAvailableException { + CommandResult result = mDevice.executeShellV2Command( + "dd if=" + filePath + " bs=1 count=1 skip=" + Long.toString(offset)); + return result.getStatus() == CommandStatus.SUCCESS; + } + + private String expectRemoteCommandToSucceed(String cmd) throws DeviceNotAvailableException { + CommandResult result = mDevice.executeShellV2Command(cmd); + assertEquals("`" + cmd + "` failed: " + result.getStderr(), CommandStatus.SUCCESS, + result.getStatus()); + return result.getStdout(); + } + + private void expectRemoteCommandToFail(String cmd) throws DeviceNotAvailableException { + CommandResult result = mDevice.executeShellV2Command(cmd); + assertTrue("Unexpected success from `" + cmd + "`: " + result.getStderr(), + result.getStatus() != CommandStatus.SUCCESS); + } + + private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> { + InstallMultiple() { + super(getDevice(), getBuild()); + } + + InstallMultiple addFileAndSignature(String filename) { + try { + addFile(filename); + addFile(filename + ".fsv_sig"); + } catch (FileNotFoundException e) { + fail("Missing test file: " + e); + } + return this; + } + } +} diff --git a/tests/ApkVerityTest/src/com/android/apkverity/BaseInstallMultiple.java b/tests/ApkVerityTest/src/com/android/apkverity/BaseInstallMultiple.java new file mode 100644 index 000000000000..02e73d157dde --- /dev/null +++ b/tests/ApkVerityTest/src/com/android/apkverity/BaseInstallMultiple.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.apkverity; + +import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; +import com.android.tradefed.build.IBuildInfo; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; + +import junit.framework.TestCase; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Base class for invoking the install-multiple command via ADB. Subclass this for less typing: + * + * <code> private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> { public + * InstallMultiple() { super(getDevice(), null); } } </code> + */ +/*package*/ class BaseInstallMultiple<T extends BaseInstallMultiple<?>> { + + private final ITestDevice mDevice; + private final IBuildInfo mBuild; + + private final List<String> mArgs = new ArrayList<>(); + private final Map<File, String> mFileToRemoteMap = new HashMap<>(); + + /*package*/ BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo) { + mDevice = device; + mBuild = buildInfo; + addArg("-g"); + } + + T addArg(String arg) { + mArgs.add(arg); + return (T) this; + } + + T addFile(String filename) throws FileNotFoundException { + return addFile(filename, filename); + } + + T addFile(String filename, String remoteName) throws FileNotFoundException { + CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild); + mFileToRemoteMap.put(buildHelper.getTestFile(filename), remoteName); + return (T) this; + } + + T inheritFrom(String packageName) { + addArg("-r"); + addArg("-p " + packageName); + return (T) this; + } + + void run() throws DeviceNotAvailableException { + run(true); + } + + void runExpectingFailure() throws DeviceNotAvailableException { + run(false); + } + + private void run(boolean expectingSuccess) throws DeviceNotAvailableException { + final ITestDevice device = mDevice; + + // Create an install session + final StringBuilder cmd = new StringBuilder(); + cmd.append("pm install-create"); + for (String arg : mArgs) { + cmd.append(' ').append(arg); + } + + String result = device.executeShellCommand(cmd.toString()); + TestCase.assertTrue(result, result.startsWith("Success")); + + final int start = result.lastIndexOf("["); + final int end = result.lastIndexOf("]"); + int sessionId = -1; + try { + if (start != -1 && end != -1 && start < end) { + sessionId = Integer.parseInt(result.substring(start + 1, end)); + } + } catch (NumberFormatException e) { + throw new IllegalStateException("Failed to parse install session: " + result); + } + if (sessionId == -1) { + throw new IllegalStateException("Failed to create install session: " + result); + } + + // Push our files into session. Ideally we'd use stdin streaming, + // but ddmlib doesn't support it yet. + for (final Map.Entry<File, String> entry : mFileToRemoteMap.entrySet()) { + final File file = entry.getKey(); + final String remoteName = entry.getValue(); + final String remotePath = "/data/local/tmp/" + file.getName(); + if (!device.pushFile(file, remotePath)) { + throw new IllegalStateException("Failed to push " + file); + } + + cmd.setLength(0); + cmd.append("pm install-write"); + cmd.append(' ').append(sessionId); + cmd.append(' ').append(remoteName); + cmd.append(' ').append(remotePath); + + result = device.executeShellCommand(cmd.toString()); + TestCase.assertTrue(result, result.startsWith("Success")); + } + + // Everything staged; let's pull trigger + cmd.setLength(0); + cmd.append("pm install-commit"); + cmd.append(' ').append(sessionId); + + result = device.executeShellCommand(cmd.toString()); + if (expectingSuccess) { + TestCase.assertTrue(result, result.contains("Success")); + } else { + TestCase.assertFalse(result, result.contains("Success")); + } + } +} diff --git a/tests/ApkVerityTest/testdata/Android.bp b/tests/ApkVerityTest/testdata/Android.bp new file mode 100644 index 000000000000..c10b0cef21d7 --- /dev/null +++ b/tests/ApkVerityTest/testdata/Android.bp @@ -0,0 +1,77 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +filegroup { + name: "ApkVerityTestKeyPem", + srcs: ["ApkVerityTestKey.pem"], +} + +filegroup { + name: "ApkVerityTestCertPem", + srcs: ["ApkVerityTestCert.pem"], +} + +filegroup { + name: "ApkVerityTestCertDer", + srcs: ["ApkVerityTestCert.der"], +} + +filegroup { + name: "ApkVerityTestAppDm", + srcs: ["ApkVerityTestApp.dm"], +} + +filegroup { + name: "ApkVerityTestAppSplitDm", + srcs: ["ApkVerityTestAppSplit.dm"], +} + +genrule_defaults { + name: "apk_verity_sig_gen_default", + tools: ["fsverity"], + tool_files: [":ApkVerityTestKeyPem", ":ApkVerityTestCertPem"], + cmd: "$(location fsverity) sign $(in) $(out) " + + "--key=$(location :ApkVerityTestKeyPem) " + + "--cert=$(location :ApkVerityTestCertPem) " + + "> /dev/null", +} + +genrule { + name: "ApkVerityTestAppFsvSig", + defaults: ["apk_verity_sig_gen_default"], + srcs: [":ApkVerityTestApp"], + out: ["ApkVerityTestApp.apk.fsv_sig"], +} + +genrule { + name: "ApkVerityTestAppDmFsvSig", + defaults: ["apk_verity_sig_gen_default"], + srcs: [":ApkVerityTestAppDm"], + out: ["ApkVerityTestApp.dm.fsv_sig"], +} + +genrule { + name: "ApkVerityTestAppSplitFsvSig", + defaults: ["apk_verity_sig_gen_default"], + srcs: [":ApkVerityTestAppSplit"], + out: ["ApkVerityTestAppSplit.apk.fsv_sig"], +} + +genrule { + name: "ApkVerityTestAppSplitDmFsvSig", + defaults: ["apk_verity_sig_gen_default"], + srcs: [":ApkVerityTestAppSplitDm"], + out: ["ApkVerityTestAppSplit.dm.fsv_sig"], +} + diff --git a/tests/ApkVerityTest/testdata/ApkVerityTestApp.dm b/tests/ApkVerityTest/testdata/ApkVerityTestApp.dm Binary files differnew file mode 100644 index 000000000000..e53a86131366 --- /dev/null +++ b/tests/ApkVerityTest/testdata/ApkVerityTestApp.dm diff --git a/tests/ApkVerityTest/testdata/ApkVerityTestAppSplit.dm b/tests/ApkVerityTest/testdata/ApkVerityTestAppSplit.dm Binary files differnew file mode 100644 index 000000000000..75396f1ba730 --- /dev/null +++ b/tests/ApkVerityTest/testdata/ApkVerityTestAppSplit.dm diff --git a/tests/ApkVerityTest/testdata/ApkVerityTestCert.der b/tests/ApkVerityTest/testdata/ApkVerityTestCert.der Binary files differnew file mode 100644 index 000000000000..fe9029b53aa1 --- /dev/null +++ b/tests/ApkVerityTest/testdata/ApkVerityTestCert.der diff --git a/tests/ApkVerityTest/testdata/ApkVerityTestCert.pem b/tests/ApkVerityTest/testdata/ApkVerityTestCert.pem new file mode 100644 index 000000000000..6c0b7b1f635a --- /dev/null +++ b/tests/ApkVerityTest/testdata/ApkVerityTestCert.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFLjCCAxagAwIBAgIJAKZbtMlZZwtdMA0GCSqGSIb3DQEBCwUAMCwxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTEQMA4GA1UECgwHQW5kcm9pZDAeFw0xODEyMTky +MTA5MzVaFw0xOTAxMTgyMTA5MzVaMCwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD +QTEQMA4GA1UECgwHQW5kcm9pZDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBAKnrw4WiFgFBq6vXqcLc97iwvcYPZmeIjQqYRF+CHwXBXx8IyDlMfPrgyIYo +ZLkosnUK/Exuypdu6UEtdqtYPknC6w9z4YkxqsKtyxyB1b13ptcTHh3bf2N8bqGr +8gWWLxj0QjumCtFi7Z/TCwB5t3b3gtC+0jVfABSWrm5PNkgk7jIP+4KeYLDCDfiJ +XH3uHu6OASiSHTOnrmLWSaSw0y6G4OFthHqQnMywasly0r6m+Mif+K0ZUV7hBRi/ +SfqcJ1HTCXTJMskEyV6Qx2sHF/VbK2gdUv56z6OVRNSs/FxPBiWVMuZZKh1FpBVI +gbGxusf2Awwtc+Soxr4/P1YFcrwfA/ff9FK3Yg/Cd3ZMGbzUkbEMEkE5BW7Gbjmx +wz3mYTiRfa2L/Bl4MiMqNi0tfORLkmg+V/EItzfhZ/HsXMOCBsnuj4KnFslmbamz +t9opypj2JLGk+lXhZ5gFNFw8tYH1AnG1AIXe5u+6Fq2nQ1y/ncGUTR5Sw4de/Gee +C0UgR+KiFEdKupMKbXgSKl+0QPz/i2eSpcDOKMwZ4WiNrkbccbCyr38so+j5DfWF +IeZA9a/IlysA6G8yU2TfXBc65VCIEQRJOQdUOZFDO8OSoqGP+fbA6edpmovGw+TH +sM/NkmpEXpQm7BVOI4oVjdf4pKPp0zaW2YcaA3xU2w6eF17pAgMBAAGjUzBRMB0G +A1UdDgQWBBRGpHYy7yiLEYalGuF1va6zJKGD/zAfBgNVHSMEGDAWgBRGpHYy7yiL +EYalGuF1va6zJKGD/zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IC +AQAao6ZBM122F0pYb2QLahIyyGEr3LfSdBGID4068pVik4ncIefFz36Xf9AFxRQd +KHmwRYNPHiLRIEGdtqplC5pZDeHz41txIArNIZKzDWOYtdcFyCz8umuj912BmsoM +YUQhT6F1sX53SWcKxEP/aJ2kltSlPFX99e3Vx9eRkceV1oe2NM6ZG8hnYCfCAMeJ +jRTpbqCGaAsEHFtIx6wt3zEtUXIVg4aYFQs/qjTjeP8ByIj0b4lZrceEoTeRimuj ++4aAI+jBxLkwaN3hseQHzRNpgPehIVV/0RU92yzOD/WN4YwE6rwjKEI1lihHNBDa ++DwGtGbHmIUzjW1qArig+mzUIhfYIJAxrx20ynPz/Q+C7+iXhTDAYQlxTle0pX8m +yM2DUdPo97eLOzQ4JDHxtcN3ntTEJKKvrmzKvWuxy/yoLwS7MtLH6RETTHabH3Qd +CP83X7z8zTyxgPxHdfHo9sgR/4C9RHGJx4OpBTQaiqfjSpDqJSIQdbrHGOQDgYwL +KQyiQuhukmNgRCB6dJoZJ/MyaNuMsXV9QobsDHW1oSuCvPAihVoWHJxt8m4Ma0jJ +EIbEPT2Umw1F/P+CeXnVQwhPvzQKHCa+6cC/YdjTqIKLmQV8X3HUBUIMhP2JGDic +MnUipTm/RwWZVOjCJaFqk5sVq3L0Lyd0XVUWSK1a4IcrsA== +-----END CERTIFICATE----- diff --git a/tests/ApkVerityTest/testdata/ApkVerityTestKey.pem b/tests/ApkVerityTest/testdata/ApkVerityTestKey.pem new file mode 100644 index 000000000000..f0746c162421 --- /dev/null +++ b/tests/ApkVerityTest/testdata/ApkVerityTestKey.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCp68OFohYBQaur +16nC3Pe4sL3GD2ZniI0KmERfgh8FwV8fCMg5THz64MiGKGS5KLJ1CvxMbsqXbulB +LXarWD5JwusPc+GJMarCrcscgdW9d6bXEx4d239jfG6hq/IFli8Y9EI7pgrRYu2f +0wsAebd294LQvtI1XwAUlq5uTzZIJO4yD/uCnmCwwg34iVx97h7ujgEokh0zp65i +1kmksNMuhuDhbYR6kJzMsGrJctK+pvjIn/itGVFe4QUYv0n6nCdR0wl0yTLJBMle +kMdrBxf1WytoHVL+es+jlUTUrPxcTwYllTLmWSodRaQVSIGxsbrH9gMMLXPkqMa+ +Pz9WBXK8HwP33/RSt2IPwnd2TBm81JGxDBJBOQVuxm45scM95mE4kX2ti/wZeDIj +KjYtLXzkS5JoPlfxCLc34Wfx7FzDggbJ7o+CpxbJZm2ps7faKcqY9iSxpPpV4WeY +BTRcPLWB9QJxtQCF3ubvuhatp0Ncv53BlE0eUsOHXvxnngtFIEfiohRHSrqTCm14 +EipftED8/4tnkqXAzijMGeFoja5G3HGwsq9/LKPo+Q31hSHmQPWvyJcrAOhvMlNk +31wXOuVQiBEESTkHVDmRQzvDkqKhj/n2wOnnaZqLxsPkx7DPzZJqRF6UJuwVTiOK +FY3X+KSj6dM2ltmHGgN8VNsOnhde6QIDAQABAoICAGT21tWnisWyXKwd2BwWKgeO +1SRDcEiihZO/CBlr+rzzum55TGdngHedauj0RW0Ttn3/SgysZCp415ZHylRjeZdg +f0VOSLu5TEqi86X7q6IJ35O6I1IAY4AcpqvfvE3/f/qm4FgLADCMRL+LqeTdbdr9 +lLguOj9GNIkHQ5v96zYQ44vRnVNugetlUuHT1KZq/+wlaqDNuRZBU0gdJeL6wnDJ +6gNojKg7F0A0ry8F0B1Cn16uVxebjJMAx4N93hpQALkI2XyQNGHnOzO6eROqQl0i +j/csPW1CUfBUOHLaWpUKy483SOhAINsFz0pqK84G2gIItqTcuRksA/N1J1AYqqQO ++/8IK5Mb9j0RaYYrBG83luGCWYauAsWg2Yol6fUGju8IY/zavOaES42XogY588Ad +JzW+njjxXcnoD/u5keWrGwbPdGfoaLLg4eMlRBT4yNicyT04knXjFG4QTfLY5lF/ +VKdvZk6RMoCLdAtgN6EKHtcwuoYR967otsbavshngZ9HE/ic5/TdNFCBjxs6q9bm +docC4CLHU/feXvOCYSnIfUpDzEPV96Gbk6o0qeYn3RUSGzRpXQHxXXfszEESUWnd +2rtfXxqA7C5n8CshBfKJND7/LKRGpBRaYWJtc4hFmo8prhXfOb40PEZNlx8mcsEz +WYZpmvFQHU8+bZIm0a5RAoIBAQDaCAje9xLKN1CYzygA/U3x2CsGuWWyh9xM1oR5 +5t+nn0EeIMrzGuHrD4hdbZiTiJcO5dpSg/3dssc/QLJEdv+BoMEgSYTf3TX03dIb +kSlj+ONobejO4nVoUP0axTvVe/nuMYvLguTM6OCFvgV752TFxVyVHl6RM+rQYGCl +ajbBCsCRg4QgpZ/RHWf+3KMJunzwWBlsAXcjOudneYqEl713h/q1lc5cONIglQDU +E+bc5q6q++c/H8dYaWq4QE4CQU8wsq77/bZk8z1jheOV0HkwaH5ShtKD7bk/4MA9 +jWQUDW6/LRXkNiPExsAZxnP3mxhtUToWq1nedF6kPmNBko+9AoIBAQDHgvAql6i7 +osTYUcY/GldPmnrvfqbKmD0nI8mnaJfN2vGwjB/ol3lm+pviKIQ4ER80xsdn4xK0 +2cC9OdkY2UX7zilKohxvKVsbVOYpUwfpoRQO1Euddb6iAMqgGDyDzRBDTzTx5pB5 +XL9B/XuJVCMkjsNdD9iEbjdY1Epv7kYf53zfvrXdqv24uSNAszPFBLLPHSC9yONE +a/t3mHGZ2cjr52leGNGY7ib6GNGBUeA34SM9g97tU9pAgy712RfZhH6fA93CLk6T +DKoch56YId71vZt2J0Lrk4TWnnpidSoRmzKfVIJwjCmgYbI+2eDp7h0Z0DnDbji6 +9BPt3RWsoZidAoIBAA2A7+O3U7+Ye3JraiPdjGVNKSUKeIT9KyTLKHtQVEvSbjsK +dudlo9ZmKOD4d7mzfP+cNtBjgmanuvVs8V2SLTL/HNb+Fq+yyLO4xVmVvQWHFbaT +EBc4KWNjmLl+u7z2J72b7feVzMvwJG/EHBzXcQNavOgzcFH38DQls/aqxGdiXhjl +F1raRzKxao57ZdGlbjWIj1KEKLfS3yAmg/DAYSi1EE8MzzIhBsqjz+BStzq5Qtou +Ld1X/4W3SbfNq8cx+lCe0H2k8hYAhq3STg0qU0cvQZuk5Abtw0p0hhOJ3UfsqQ5I +IZH31HFMiftOskIEphenLzzWMgO4G2B6yLT3+dUCggEAOLF1i7Ti5sbfBtVd70qN +6vnr2yhzPvi5z+h0ghTPpliD+3YmDxMUFXY7W63FvKTo6DdgLJ4zD58dDOhmT5BW +ObKguyuLxu7Ki965NJ76jaIPMBOVlR4DWMe+zHV2pMFd0LKuSdsJzOLVGmxscV6u +SdIjo8s/7InhQmW47UuZM7G1I2NvDJltVdOON/F0UZT/NqmBR0zRf/zrTVXNWjmv +xZFRuMJ2tO1fuAvbZNMeUuKv/+f8LhZ424IrkwLoqw/iZ09S8b306AZeRJMpNvPR +BqWlipKnioe15MLN5jKDDNO8M9hw5Ih/v6pjW0bQicj3DgHEmEs25bE8BIihgxe8 +ZQKCAQEAsWKsUv13OEbYYAoJgbzDesWF9NzamFB0NLyno9SChvFPH/d8RmAuti7Y +BQUoBswLK24DF/TKf1YocsZq8tu+pnv0Nx1wtK4K+J3A1BYDm7ElpO3Km+HPUBtf +C9KGT5hotlMQVTpYSDG/QeWbfl4UnNZcbg8pmv38NwV1eDoVDfaVrRYJzQn75+Tf +s/WUq1x5PElR/4pNIU2i6pJGd6FimhRweJu/INR36spWmbMRNX8fyXx+9EBqMbVp +vS2xGgxxQT6bAvBfRlpgi87T9v5Gqoy6/jM/wX9smH9PfUV1vK32n3Zrbd46gwZW +p2aUlQOLXU9SjQTirZbdCZP0XHtFsg== +-----END PRIVATE KEY----- diff --git a/tests/ApkVerityTest/testdata/README.md b/tests/ApkVerityTest/testdata/README.md new file mode 100644 index 000000000000..163cb183a5ad --- /dev/null +++ b/tests/ApkVerityTest/testdata/README.md @@ -0,0 +1,13 @@ +This test only runs on rooted / debuggable device. + +The test tries to install subsets of base.{apk,dm}, split.{apk,dm} and their +corresponding .fsv_sig files (generated by build rule). If installed, the +tests also tries to tamper with the file at absolute disk offset to verify +if fs-verity is effective. + +How to generate dex metadata (.dm) +================================== + + adb shell profman --generate-test-profile=/data/local/tmp/primary.prof + adb pull /data/local/tmp/primary.prof + zip foo.dm primary.prof diff --git a/tests/AppLaunch/Android.bp b/tests/AppLaunch/Android.bp index f90f26f00e6d..75db55122553 100644 --- a/tests/AppLaunch/Android.bp +++ b/tests/AppLaunch/Android.bp @@ -8,6 +8,8 @@ android_test { "android.test.base", "android.test.runner", ], - static_libs: ["androidx.test.rules"], + static_libs: [ + "androidx.test.rules", + "ub-uiautomator"], test_suites: ["device-tests"], } diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java index 95b8f6700c76..1a58f17ef6a0 100644 --- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java +++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java @@ -15,6 +15,8 @@ */ package com.android.tests.applaunch; +import static org.junit.Assert.assertNotNull; + import android.accounts.Account; import android.accounts.AccountManager; import android.app.ActivityManager; @@ -29,7 +31,9 @@ import android.content.pm.ResolveInfo; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.os.SystemClock; import android.os.UserHandle; +import android.support.test.uiautomator.UiDevice; import android.test.InstrumentationTestCase; import android.test.InstrumentationTestRunner; import android.util.Log; @@ -41,11 +45,17 @@ import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; +import java.nio.file.Paths; +import java.time.format.DateTimeFormatter; +import java.time.ZonedDateTime; +import java.time.ZoneOffset; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -62,6 +72,7 @@ import java.util.Set; * in the following format: * -e apps <app name>^<result key>|<app name>^<result key> */ +@Deprecated public class AppLaunch extends InstrumentationTestCase { private static final int JOIN_TIMEOUT = 10000; @@ -71,6 +82,8 @@ public class AppLaunch extends InstrumentationTestCase { // with the app launch private static final String KEY_REQUIRED_ACCOUNTS = "required_accounts"; private static final String KEY_APPS = "apps"; + private static final String KEY_IORAP_TRIAL_LAUNCH = "iorap_trial_launch"; + private static final String KEY_IORAP_COMPILER_FILTERS = "iorap_compiler_filters"; private static final String KEY_TRIAL_LAUNCH = "trial_launch"; private static final String KEY_LAUNCH_ITERATIONS = "launch_iterations"; private static final String KEY_LAUNCH_ORDER = "launch_order"; @@ -87,6 +100,9 @@ public class AppLaunch extends InstrumentationTestCase { private static final String KEY_TRACE_DUMPINTERVAL = "tracedump_interval"; private static final String KEY_COMPILER_FILTERS = "compiler_filters"; private static final String KEY_FORCE_STOP_APP = "force_stop_app"; + private static final String ENABLE_SCREEN_RECORDING = "enable_screen_recording"; + private static final int MAX_RECORDING_PARTS = 5; + private static final long VIDEO_TAIL_BUFFER = 500; private static final String SIMPLEPERF_APP_CMD = "simpleperf --log fatal stat --csv -e cpu-cycles,major-faults --app %s & %s"; @@ -98,6 +114,11 @@ public class AppLaunch extends InstrumentationTestCase { private static final int BEFORE_KILL_APP_SLEEP_TIMEOUT = 1000; // 1s before killing private static final int BETWEEN_LAUNCH_SLEEP_TIMEOUT = 3000; // 3s between launching apps private static final int PROFILE_SAVE_SLEEP_TIMEOUT = 1000; // Allow 1s for the profile to save + private static final int IORAP_TRACE_DURATION_TIMEOUT = 7000; // Allow 7s for trace to complete. + private static final int IORAP_TRIAL_LAUNCH_ITERATIONS = 5; // min 5 launches to merge traces. + private static final int IORAP_COMPILE_CMD_TIMEOUT = 60; // in seconds: 1 minutes + private static final int IORAP_COMPILE_MIN_TRACES = 1; // configure iorapd to need 1 trace. + private static final int IORAP_COMPILE_RETRIES = 3; // retry compiler 3 times if it fails. private static final String LAUNCH_SUB_DIRECTORY = "launch_logs"; private static final String LAUNCH_FILE = "applaunch.txt"; private static final String TRACE_SUB_DIRECTORY = "atrace_logs"; @@ -106,6 +127,9 @@ public class AppLaunch extends InstrumentationTestCase { private static final String DEFAULT_TRACE_BUFFER_SIZE = "20000"; private static final String DEFAULT_TRACE_DUMP_INTERVAL = "10"; private static final String TRIAL_LAUNCH = "TRIAL_LAUNCH"; + private static final String IORAP_TRIAL_LAUNCH = "IORAP_TRIAL_LAUNCH"; + private static final String IORAP_TRIAL_LAUNCH_FIRST = "IORAP_TRIAL_LAUNCH_FIRST"; + private static final String IORAP_TRIAL_LAUNCH_LAST = "IORAP_TRIAL_LAUNCH_LAST"; private static final String DELIMITER = ","; private static final String DROP_CACHE_SCRIPT = "/data/local/tmp/dropCache.sh"; private static final String APP_LAUNCH_CMD = "am start -W -n"; @@ -119,32 +143,49 @@ public class AppLaunch extends InstrumentationTestCase { private static final String LAUNCH_ORDER_CYCLIC = "cyclic"; private static final String LAUNCH_ORDER_SEQUENTIAL = "sequential"; private static final String COMPILE_CMD = "cmd package compile -f -m %s %s"; + private static final String IORAP_COMPILE_CMD = "dumpsys iorapd --compile-package %s"; + private static final String IORAP_MAINTENANCE_CMD = + "dumpsys iorapd --purge-package %s"; + private static final String IORAP_DUMPSYS_CMD = "dumpsys iorapd"; private static final String SPEED_PROFILE_FILTER = "speed-profile"; private static final String VERIFY_FILTER = "verify"; private static final String LAUNCH_SCRIPT_NAME = "appLaunch"; private Map<String, Intent> mNameToIntent; private List<LaunchOrder> mLaunchOrderList = new ArrayList<LaunchOrder>(); + private RecordingThread mCurrentThread; private Map<String, String> mNameToResultKey; private Map<String, Map<String, List<AppLaunchResult>>> mNameToLaunchTime; private IActivityManager mAm; + private File launchSubDir = null; private String mSimplePerfCmd = null; private String mLaunchOrder = null; private boolean mDropCache = false; private int mLaunchIterations = 10; private boolean mForceStopApp = true; + private boolean mEnableRecording = false; private int mTraceLaunchCount = 0; private String mTraceDirectoryStr = null; private Bundle mResult = new Bundle(); private Set<String> mRequiredAccounts; private boolean mTrialLaunch = false; + private boolean mIorapTrialLaunch = false; private BufferedWriter mBufferedWriter = null; private boolean mSimplePerfAppOnly = false; private String[] mCompilerFilters = null; + private List<String> mIorapCompilerFilters = null; private String mLastAppName = ""; private boolean mCycleCleanUp = false; private boolean mTraceAll = false; private boolean mIterationCycle = false; + private UiDevice mDevice; + + enum IorapStatus { + UNDEFINED, + ENABLED, + DISABLED + } + private IorapStatus mIorapStatus = IorapStatus.UNDEFINED; private long mCycleTime = 0; private StringBuilder mCycleTimes = new StringBuilder(); @@ -194,7 +235,7 @@ public class AppLaunch extends InstrumentationTestCase { } try { - File launchSubDir = new File(launchRootDir, LAUNCH_SUB_DIRECTORY); + launchSubDir = new File(launchRootDir, LAUNCH_SUB_DIRECTORY); if (!launchSubDir.exists() && !launchSubDir.mkdirs()) { throw new IOException("Unable to create the lauch file sub directory " @@ -243,7 +284,10 @@ public class AppLaunch extends InstrumentationTestCase { setLaunchOrder(); for (LaunchOrder launch : mLaunchOrderList) { - dropCache(); + toggleIorapStatus(launch.getIorapEnabled()); + dropCache(/*override*/false); + + Log.v(TAG, "Launch reason: " + launch.getLaunchReason()); // App launch times for trial launch will not be used for final // launch time calculations. @@ -289,6 +333,43 @@ public class AppLaunch extends InstrumentationTestCase { compileApp(launch.getCompilerFilter(), appPkgName)); } } + else if (launch.getLaunchReason().startsWith(IORAP_TRIAL_LAUNCH)) { + mIterationCycle = false; + + // In the "applaunch.txt" file, iorap-trial launches is referenced using + // "IORAP_TRIAL_LAUNCH" or "IORAP_TRIAL_LAUNCH_LAST" + Intent startIntent = mNameToIntent.get(launch.getApp()); + if (startIntent == null) { + Log.w(TAG, "App does not exist: " + launch.getApp()); + mResult.putString(mNameToResultKey.get(launch.getApp()), + "App does not exist"); + continue; + } + String appPkgName = startIntent.getComponent().getPackageName(); + + if (launch.getLaunchReason().equals(IORAP_TRIAL_LAUNCH_FIRST)) { + // delete any iorap-traces associated with this package. + purgeIorapPackage(appPkgName); + } + dropCache(/*override*/true); // iorap-trial runs must have drop cache. + + AppLaunchResult launchResult = + startApp(launch.getApp(), launch.getLaunchReason()); + if (launchResult.mLaunchTime < 0) { + addLaunchResult(launch, new AppLaunchResult()); + // simply pass the app if launch isn't successful + // error should have already been logged by startApp + continue; + } + // wait for slightly more than 5s (iorapd.perfetto.trace_duration_ms) for the trace buffers to complete. + sleep(IORAP_TRACE_DURATION_TIMEOUT); + + if (launch.getLaunchReason().equals(IORAP_TRIAL_LAUNCH_LAST)) { + // run the iorap compiler and wait for iorap to compile fully. + // this throws an exception if it fails. + compileAppForIorapWithRetries(appPkgName, IORAP_COMPILE_RETRIES); + } + } // App launch times used for final calculation else if (launch.getLaunchReason().contains(LAUNCH_ITERATION_PREFIX)) { @@ -296,6 +377,8 @@ public class AppLaunch extends InstrumentationTestCase { AppLaunchResult launchResults = null; if (hasFailureOnFirstLaunch(launch)) { // skip if the app has failures while launched first + Log.w(TAG, "Has failures on first launch: " + launch.getApp()); + forceStopApp(launch.getApp()); continue; } AtraceLogger atraceLogger = null; @@ -438,6 +521,168 @@ public class AppLaunch extends InstrumentationTestCase { } /** + * Compile the app package using compilerFilter, + * retrying if the compilation command fails in between. + */ + private void compileAppForIorapWithRetries(String appPkgName, int retries) throws IOException { + for (int i = 0; i < retries; ++i) { + if (compileAppForIorap(appPkgName)) { + return; + } + sleep(1000); + } + + throw new IllegalStateException("compileAppForIorapWithRetries: timed out after " + + retries + " retries"); + } + + /** + * Compile the app package using compilerFilter and return true or false + * based on status of the compilation command. + */ + private boolean compileAppForIorap(String appPkgName) throws IOException { + String logcatTimestamp = getTimeNowForLogcat(); + + getInstrumentation().getUiAutomation(). + executeShellCommand(String.format(IORAP_COMPILE_CMD, appPkgName)); + + int i = 0; + for (i = 0; i < IORAP_COMPILE_CMD_TIMEOUT; ++i) { + IorapCompilationStatus status = waitForIorapCompiled(appPkgName); + if (status == IorapCompilationStatus.COMPLETE) { + Log.v(TAG, "compileAppForIorap: success"); + logDumpsysIorapd(appPkgName); + break; + } else if (status == IorapCompilationStatus.INSUFFICIENT_TRACES) { + Log.e(TAG, "compileAppForIorap: failed due to insufficient traces"); + logDumpsysIorapd(appPkgName); + throw new IllegalStateException( + "compileAppForIorap: failed due to insufficient traces"); + } // else INCOMPLETE. keep asking iorapd if it's done yet. + sleep(1000); + } + + if (i == IORAP_COMPILE_CMD_TIMEOUT) { + Log.e(TAG, "compileAppForIorap: failed due to timeout"); + logDumpsysIorapd(appPkgName); + return false; + } + + return true; + } + + /** Save the contents of $(adb shell dumpsys iorapd) to the launch_logs directory. */ + private void logDumpsysIorapd(String packageName) throws IOException { + InstrumentationTestRunner instrumentation = + (InstrumentationTestRunner)getInstrumentation(); + Bundle args = instrumentation.getArguments(); + + String launchDirectory = args.getString(KEY_LAUNCH_DIRECTORY); + + // Root directory for applaunch file to log the app launch output + // Will be useful in case of simpleperf command is used + File launchRootDir = null; + if (null != launchDirectory && !launchDirectory.isEmpty()) { + launchRootDir = new File(launchDirectory); + if (!launchRootDir.exists() && !launchRootDir.mkdirs()) { + throw new IOException("Unable to create the destination directory " + + launchRootDir + ". Try disabling selinux."); + } + } else { + Log.w(TAG, "logDumpsysIorapd: Missing launch-directory arg"); + return; + } + + File launchSubDir = new File(launchRootDir, LAUNCH_SUB_DIRECTORY); + + if (!launchSubDir.exists() && !launchSubDir.mkdirs()) { + throw new IOException("Unable to create the lauch file sub directory " + + launchSubDir + ". Try disabling selinux."); + } + String path = "iorapd_dumpsys_" + packageName + "_" + System.nanoTime() + ".txt"; + File file = new File(launchSubDir, path); + try (FileOutputStream outputStream = new FileOutputStream(file); + BufferedWriter writer = new BufferedWriter( + new OutputStreamWriter(outputStream)); + ParcelFileDescriptor result = getInstrumentation().getUiAutomation(). + executeShellCommand(IORAP_DUMPSYS_CMD); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( + new FileInputStream(result.getFileDescriptor())))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + writer.write(line + "\n"); + } + } + + Log.v(TAG, "logDumpsysIorapd: Saved to file: " + path); + } + + enum IorapCompilationStatus { + INCOMPLETE, + COMPLETE, + INSUFFICIENT_TRACES, + } + private IorapCompilationStatus waitForIorapCompiled(String appPkgName) throws IOException { + try (ParcelFileDescriptor result = getInstrumentation().getUiAutomation(). + executeShellCommand(IORAP_DUMPSYS_CMD); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( + new FileInputStream(result.getFileDescriptor())))) { + String line; + String prevLine = ""; + while ((line = bufferedReader.readLine()) != null) { + // Match the indented VersionedComponentName string. + // " com.google.android.deskclock/com.android.deskclock.DeskClock@62000712" + // Note: spaces are meaningful here. + if (prevLine.contains(" " + appPkgName) && prevLine.contains("@")) { + // pre-requisite: + // Compiled Status: Raw traces pending compilation (3) + if (line.contains("Compiled Status: Usable compiled trace")) { + return IorapCompilationStatus.COMPLETE; + } else if (line.contains("Compiled Status: ") && + line.contains("more traces for compilation")) { + // Compiled Status: Need 1 more traces for compilation + // No amount of waiting will help here because there were + // insufficient traces made. + return IorapCompilationStatus.INSUFFICIENT_TRACES; + } + } + + prevLine = line; + } + return IorapCompilationStatus.INCOMPLETE; + } + } + + private String makeReasonForIorapTrialLaunch(int launchCount) { + String reason = IORAP_TRIAL_LAUNCH; + if (launchCount == 0) { + reason = IORAP_TRIAL_LAUNCH_FIRST; + } + if (launchCount == IORAP_TRIAL_LAUNCH_ITERATIONS - 1) { + reason = IORAP_TRIAL_LAUNCH_LAST; + } + return reason; + } + + private boolean shouldIncludeIorap(String compilerFilter) { + if (!mIorapTrialLaunch) { + return false; + } + + // No iorap compiler filters specified: treat all compiler filters as ok. + if (mIorapCompilerFilters == null) { + return true; + } + + // iorap compiler filters specified: the compilerFilter must be in the whitelist. + if (mIorapCompilerFilters.indexOf(compilerFilter) != -1) { + return true; + } + + return false; + } + + /** * If launch order is "cyclic" then apps will be launched one after the * other for each iteration count. * If launch order is "sequential" then each app will be launched for given number @@ -448,20 +693,33 @@ public class AppLaunch extends InstrumentationTestCase { for (String compilerFilter : mCompilerFilters) { if (mTrialLaunch) { for (String app : mNameToResultKey.keySet()) { - mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, TRIAL_LAUNCH)); + mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, TRIAL_LAUNCH, /*iorapEnabled*/false)); + } + } + if (shouldIncludeIorap(compilerFilter)) { + for (int launchCount = 0; launchCount < IORAP_TRIAL_LAUNCH_ITERATIONS; ++launchCount) { + for (String app : mNameToResultKey.keySet()) { + String reason = makeReasonForIorapTrialLaunch(launchCount); + mLaunchOrderList.add( + new LaunchOrder(app, compilerFilter, + reason, + /*iorapEnabled*/true)); + } } } for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) { for (String app : mNameToResultKey.keySet()) { mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, - String.format(LAUNCH_ITERATION, launchCount))); + String.format(LAUNCH_ITERATION, launchCount), + shouldIncludeIorap(compilerFilter))); } } if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) { for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) { for (String app : mNameToResultKey.keySet()) { mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, - String.format(TRACE_ITERATION, traceCount))); + String.format(TRACE_ITERATION, traceCount), + shouldIncludeIorap(compilerFilter))); } } } @@ -470,16 +728,27 @@ public class AppLaunch extends InstrumentationTestCase { for (String compilerFilter : mCompilerFilters) { for (String app : mNameToResultKey.keySet()) { if (mTrialLaunch) { - mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, TRIAL_LAUNCH)); + mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, TRIAL_LAUNCH, /*iorapEnabled*/false)); + } + if (shouldIncludeIorap(compilerFilter)) { + for (int launchCount = 0; launchCount < IORAP_TRIAL_LAUNCH_ITERATIONS; ++launchCount) { + String reason = makeReasonForIorapTrialLaunch(launchCount); + mLaunchOrderList.add( + new LaunchOrder(app, compilerFilter, + reason, + /*iorapEnabled*/true)); + } } for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) { mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, - String.format(LAUNCH_ITERATION, launchCount))); + String.format(LAUNCH_ITERATION, launchCount), + shouldIncludeIorap(compilerFilter))); } if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) { for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) { mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, - String.format(TRACE_ITERATION, traceCount))); + String.format(TRACE_ITERATION, traceCount), + shouldIncludeIorap(compilerFilter))); } } } @@ -489,14 +758,176 @@ public class AppLaunch extends InstrumentationTestCase { } } - private void dropCache() { - if (mDropCache) { + private void dropCache(boolean override) { + if (mDropCache || override) { assertNotNull("Issue in dropping the cache", getInstrumentation().getUiAutomation() .executeShellCommand(DROP_CACHE_SCRIPT)); } } + // [[ $(adb shell whoami) == "root" ]] + private boolean checkIfRoot() throws IOException { + String total = ""; + try (ParcelFileDescriptor result = getInstrumentation().getUiAutomation(). + executeShellCommand("whoami"); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( + new FileInputStream(result.getFileDescriptor())))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + total = total + line; + } + } + return total.contains("root"); + } + + private void stopIorapd() { + getInstrumentation().getUiAutomation() + .executeShellCommand("stop iorapd"); + sleep(100); // give it extra time to fully stop. + } + + private void startIorapd() { + String logcatTimeNow = getTimeNowForLogcat(); + Log.v(TAG, "startIorapd, logcat time: " + logcatTimeNow); + + getInstrumentation().getUiAutomation() + .executeShellCommand("start iorapd"); + + int maxAttempts = 100; + int attempt = 0; + do { + // Ensure that IorapForwardingService fully reconnects to iorapd before proceeding. + String needle = "Connected to iorapd native service"; + String logcatLines = getLogcatSinceTime(logcatTimeNow); + + if (logcatLines.contains(needle)) { + break; + } + + sleep(1000); + attempt++; + } while (attempt < maxAttempts); + + if (attempt == maxAttempts) { + Log.e(TAG, "Timed out after waiting for iorapd to start"); + } + // Wait a little bit longer for iorapd to settle. + sleep(1000); + } + + // Delete all db rows and files associated with a package in iorapd. + // Effectively deletes any raw or compiled trace files, unoptimizing the package in iorap. + private void purgeIorapPackage(String packageName) { + try { + if (!checkIfRoot()) { + throw new AssertionError("must be root to toggle iorapd; try adb root?"); + } + } catch (IOException e) { + throw new AssertionError(e); + } + + Log.v(TAG, "Purge iorap package: " + packageName); + getInstrumentation().getUiAutomation() + .executeShellCommand(String.format(IORAP_MAINTENANCE_CMD, packageName)); + Log.v(TAG, "Executed: " + String.format(IORAP_MAINTENANCE_CMD, packageName)); + } + + String executeShellCommandWithTempFile(String cmd) { + Log.v(TAG, "executeShellCommandWithTempFile, cmd: " + cmd); + try { + //File outputDir = + // InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(); + File outputFile = File.createTempFile("exec_shell_command", ".sh"); + + try { + outputFile.setWritable(true); + outputFile.setExecutable(true, /*ownersOnly*/false); + + String scriptPath = outputFile.toString(); + + // If this works correctly, the next log-line will print 'Success'. + try (BufferedWriter writer = new BufferedWriter(new FileWriter(scriptPath))) { + writer.write(cmd); + } + + String resultString = ""; + try (ParcelFileDescriptor result = getInstrumentation().getUiAutomation(). + executeShellCommand(scriptPath); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( + new FileInputStream(result.getFileDescriptor())))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + resultString += line + "\n"; + } + } + + return resultString; + } finally { + outputFile.delete(); + } + } catch (IOException e) { + throw new AssertionError("Failed to execute shell command: " + cmd, e); + } + } + + // Get the 'now' timestamp usable with $(adb logcat -v utc -T "time string") + String getTimeNowForLogcat() { + ZonedDateTime utc = ZonedDateTime.now(ZoneOffset.UTC); + + // YYYY-MM-DD hh:mm:ss.mmm + return utc.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")); + } + + String getLogcatSinceTime(String logcatTime) { + // The time has spaces in it but must be passed as a single arg. + // Therefore use a temp script file. + return executeShellCommandWithTempFile( + String.format("logcat -d -v threadtime -v utc -T '%s'", logcatTime)); + } + + /** + * Toggle iorapd-based readahead and trace-collection. + * If iorapd is already enabled and enable is true, does nothing. + * If iorapd is already disabled and enable is false, does nothing. + */ + private void toggleIorapStatus(boolean enable) { + boolean currentlyEnabled = false; + Log.v(TAG, "toggleIorapStatus " + Boolean.toString(enable)); + + // Do nothing if we are already enabled or disabled. + if (mIorapStatus == IorapStatus.ENABLED && enable) { + return; + } else if (mIorapStatus == IorapStatus.DISABLED && !enable) { + return; + } + + try { + if (!checkIfRoot()) { + throw new AssertionError("must be root to toggle iorapd; try adb root?"); + } + } catch (IOException e) { + throw new AssertionError(e); + } + + getInstrumentation().getUiAutomation() + .executeShellCommand(String.format("setprop iorapd.perfetto.enable %b", enable)); + getInstrumentation().getUiAutomation() + .executeShellCommand(String.format("setprop iorapd.readahead.enable %b", enable)); + getInstrumentation().getUiAutomation() + .executeShellCommand(String.format( + "setprop iorapd.maintenance.min_traces %d", IORAP_COMPILE_MIN_TRACES)); + // this last command blocks until iorapd refreshes its system properties + getInstrumentation().getUiAutomation() + .executeShellCommand(String.format("dumpsys iorapd --refresh-properties")); + + if (enable) { + mIorapStatus = IorapStatus.ENABLED; + } else { + mIorapStatus = IorapStatus.DISABLED; + } + } + private void parseArgs(Bundle args) { mNameToResultKey = new LinkedHashMap<String, String>(); mNameToLaunchTime = new HashMap<>(); @@ -505,9 +936,16 @@ public class AppLaunch extends InstrumentationTestCase { mLaunchIterations = Integer.parseInt(launchIterations); } String forceStopApp = args.getString(KEY_FORCE_STOP_APP); + if (forceStopApp != null) { mForceStopApp = Boolean.parseBoolean(forceStopApp); } + + String enableRecording = args.getString(ENABLE_SCREEN_RECORDING); + + if (enableRecording != null) { + mEnableRecording = Boolean.parseBoolean(enableRecording); + } String appList = args.getString(KEY_APPS); if (appList == null) return; @@ -543,6 +981,13 @@ public class AppLaunch extends InstrumentationTestCase { mCompilerFilters = new String[1]; } + String iorapCompilerFilterList = args.getString(KEY_IORAP_COMPILER_FILTERS); + if (iorapCompilerFilterList != null) { + // Passing in iorap compiler filters implies an iorap trial launch. + mIorapTrialLaunch = true; + mIorapCompilerFilters = Arrays.asList(iorapCompilerFilterList.split("\\|")); + } + // Pre-populate the results map to avoid null checks. for (String app : mNameToLaunchTime.keySet()) { HashMap<String, List<AppLaunchResult>> map = new HashMap<>(); @@ -560,6 +1005,8 @@ public class AppLaunch extends InstrumentationTestCase { mCycleCleanUp = Boolean.parseBoolean(args.getString(KEY_CYCLE_CLEAN)); mTraceAll = Boolean.parseBoolean(args.getString(KEY_TRACE_ALL)); mTrialLaunch = mTrialLaunch || Boolean.parseBoolean(args.getString(KEY_TRIAL_LAUNCH)); + mIorapTrialLaunch = mIorapTrialLaunch || + Boolean.parseBoolean(args.getString(KEY_IORAP_TRIAL_LAUNCH)); if (mSimplePerfCmd != null && mSimplePerfAppOnly) { Log.w(TAG, String.format("Passing both %s and %s is not supported, ignoring %s", @@ -611,6 +1058,9 @@ public class AppLaunch extends InstrumentationTestCase { private AppLaunchResult startApp(String appName, String launchReason) throws NameNotFoundException, RemoteException { Log.i(TAG, "Starting " + appName); + if(mEnableRecording) { + startRecording(appName, launchReason); + } Intent startIntent = mNameToIntent.get(appName); if (startIntent == null) { @@ -626,6 +1076,10 @@ public class AppLaunch extends InstrumentationTestCase { } catch (InterruptedException e) { // ignore } + + if(mEnableRecording) { + stopRecording(); + } return runnable.getResult(); } @@ -738,11 +1192,13 @@ public class AppLaunch extends InstrumentationTestCase { private String mApp; private String mCompilerFilter; private String mLaunchReason; + private boolean mIorapEnabled; - LaunchOrder(String app, String compilerFilter, String launchReason){ + LaunchOrder(String app, String compilerFilter, String launchReason, boolean iorapEnabled) { mApp = app; mCompilerFilter = compilerFilter; mLaunchReason = launchReason; + mIorapEnabled = iorapEnabled; } public String getApp() { @@ -764,6 +1220,14 @@ public class AppLaunch extends InstrumentationTestCase { public void setLaunchReason(String launchReason) { mLaunchReason = launchReason; } + + public void setIorapEnabled(boolean iorapEnabled) { + mIorapEnabled = iorapEnabled; + } + + public boolean getIorapEnabled() { + return mIorapEnabled; + } } private class AppLaunchResult { @@ -923,4 +1387,126 @@ public class AppLaunch extends InstrumentationTestCase { } } + + /** + * Start the screen recording while launching the app. + * + * @param appName + * @param launchReason + */ + private void startRecording(String appName, String launchReason) { + Log.v(TAG, "Started Recording"); + mCurrentThread = new RecordingThread("test-screen-record", + String.format("%s_%s", appName, launchReason)); + mCurrentThread.start(); + } + + /** + * Stop already started screen recording. + */ + private void stopRecording() { + // Skip if not directory. + if (launchSubDir == null) { + return; + } + + // Add some extra time to the video end. + SystemClock.sleep(VIDEO_TAIL_BUFFER); + // Ctrl + C all screen record processes. + mCurrentThread.cancel(); + // Wait for the thread to completely die. + try { + mCurrentThread.join(); + } catch (InterruptedException ex) { + Log.e(TAG, "Interrupted when joining the recording thread.", ex); + } + Log.v(TAG, "Stopped Recording"); + } + + /** Returns the recording's name for part {@code part} of launch description. */ + private File getOutputFile(String description, int part) { + // Omit the iteration number for the first iteration. + final String fileName = + String.format( + "%s-video%s.mp4", description, part == 1 ? "" : part); + return Paths.get(launchSubDir.getAbsolutePath(), description).toFile(); + } + + + /** + * Encapsulates the start and stop screen recording logic. + * Copied from ScreenRecordCollector. + */ + private class RecordingThread extends Thread { + private final String mDescription; + private final List<File> mRecordings; + + private boolean mContinue; + + public RecordingThread(String name, String description) { + super(name); + + mContinue = true; + mRecordings = new ArrayList<>(); + + assertNotNull("No test description provided for recording.", description); + mDescription = description; + } + + @Override + public void run() { + try { + // Start at i = 1 to encode parts as X.mp4, X2.mp4, X3.mp4, etc. + for (int i = 1; i <= MAX_RECORDING_PARTS && mContinue; i++) { + File output = getOutputFile(mDescription, i); + Log.d( + TAG, + String.format("Recording screen to %s", output.getAbsolutePath())); + mRecordings.add(output); + // Make sure not to block on this background command in the main thread so + // that the test continues to run, but block in this thread so it does not + // trigger a new screen recording session before the prior one completes. + getDevice().executeShellCommand( + String.format("screenrecord %s", output.getAbsolutePath())); + } + } catch (IOException e) { + throw new RuntimeException("Caught exception while screen recording."); + } + } + + public void cancel() { + mContinue = false; + + // Identify the screenrecord PIDs and send SIGINT 2 (Ctrl + C) to each. + try { + String[] pids = getDevice().executeShellCommand( + "pidof screenrecord").split(" "); + for (String pid : pids) { + // Avoid empty process ids, because of weird splitting behavior. + if (pid.isEmpty()) { + continue; + } + + getDevice().executeShellCommand( + String.format("kill -2 %s", pid)); + Log.d( + TAG, + String.format("Sent SIGINT 2 to screenrecord process (%s)", pid)); + } + } catch (IOException e) { + throw new RuntimeException("Failed to kill screen recording process."); + } + } + + public List<File> getRecordings() { + return mRecordings; + } + } + + public UiDevice getDevice() { + if (mDevice == null) { + mDevice = UiDevice.getInstance(getInstrumentation()); + } + return mDevice; + } } diff --git a/tests/AppResourcesLoaders/Android.bp b/tests/AppResourcesLoaders/Android.bp new file mode 100644 index 000000000000..e5739dbf181c --- /dev/null +++ b/tests/AppResourcesLoaders/Android.bp @@ -0,0 +1,22 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +android_test { + name: "AppResourcesLoaders", + srcs: ["**/*.java"], + sdk_version: "current", + java_resources: [":AppResourcesLoaders_Overlay"] +} diff --git a/tests/AppResourcesLoaders/AndroidManifest.xml b/tests/AppResourcesLoaders/AndroidManifest.xml new file mode 100644 index 000000000000..cb403b968abf --- /dev/null +++ b/tests/AppResourcesLoaders/AndroidManifest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.example.loaders"> + <application android:label="AppResourcesLoaders" + android:name=".LoadersApplication"> + <activity android:name=".LoaderActivity"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity android:name=".LoaderActivityIsolated" + android:process="com.android.phone" /> + </application> +</manifest> diff --git a/tests/AppResourcesLoaders/Overlay/Android.bp b/tests/AppResourcesLoaders/Overlay/Android.bp new file mode 100644 index 000000000000..80443f6c3c00 --- /dev/null +++ b/tests/AppResourcesLoaders/Overlay/Android.bp @@ -0,0 +1,19 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +android_test_helper_app { + name: "AppResourcesLoaders_Overlay", +} diff --git a/tests/AppResourcesLoaders/Overlay/AndroidManifest.xml b/tests/AppResourcesLoaders/Overlay/AndroidManifest.xml new file mode 100644 index 000000000000..083ba37262fd --- /dev/null +++ b/tests/AppResourcesLoaders/Overlay/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.example.loaders"> + <application android:hasCode="false" /> +</manifest> diff --git a/tests/AppResourcesLoaders/Overlay/res/values/values.xml b/tests/AppResourcesLoaders/Overlay/res/values/values.xml new file mode 100644 index 000000000000..8f6e462ffcae --- /dev/null +++ b/tests/AppResourcesLoaders/Overlay/res/values/values.xml @@ -0,0 +1,20 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <string name="loader_present">Loaders present: true</string> + <public type="string" name="loader_present" id="0x7f010000" /> +</resources>
\ No newline at end of file diff --git a/tests/AppResourcesLoaders/res/layout/activity_isolated.xml b/tests/AppResourcesLoaders/res/layout/activity_isolated.xml new file mode 100644 index 000000000000..0a13f00633f6 --- /dev/null +++ b/tests/AppResourcesLoaders/res/layout/activity_isolated.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="match_parent" + android:layout_width="match_parent" + android:gravity="center" + android:orientation="vertical"> + + <TextView android:text="@string/loader_present" + style="@style/ButtonStyle"/> +</LinearLayout>
\ No newline at end of file diff --git a/tests/AppResourcesLoaders/res/layout/activity_main.xml b/tests/AppResourcesLoaders/res/layout/activity_main.xml new file mode 100644 index 000000000000..7277700cb14c --- /dev/null +++ b/tests/AppResourcesLoaders/res/layout/activity_main.xml @@ -0,0 +1,29 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="match_parent" + android:layout_width="match_parent" + android:gravity="center" + android:orientation="vertical"> + + <TextView android:text="@string/loader_present" + style="@style/ButtonStyle"/> + + <Button android:id="@+id/btn_isolated_activity" + android:text="Launch Isolated Activity" + style="@style/ButtonStyle"/> +</LinearLayout>
\ No newline at end of file diff --git a/tests/AppResourcesLoaders/res/values/styles.xml b/tests/AppResourcesLoaders/res/values/styles.xml new file mode 100644 index 000000000000..ee73d65a21a6 --- /dev/null +++ b/tests/AppResourcesLoaders/res/values/styles.xml @@ -0,0 +1,23 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <style name="ButtonStyle" > + <item name="android:layout_width">wrap_content</item> + <item name="android:layout_height">40dp</item> + <item name="android:layout_centerInParent">true</item> + </style> +</resources>
\ No newline at end of file diff --git a/tests/AppResourcesLoaders/res/values/values.xml b/tests/AppResourcesLoaders/res/values/values.xml new file mode 100644 index 000000000000..af128b6e0ac9 --- /dev/null +++ b/tests/AppResourcesLoaders/res/values/values.xml @@ -0,0 +1,20 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <string name="loader_present">Loaders present: false</string> + <public type="string" name="loader_present" id="0x7f010000" /> +</resources>
\ No newline at end of file diff --git a/tests/AppResourcesLoaders/src/com/android/example/loaders/LoaderActivity.java b/tests/AppResourcesLoaders/src/com/android/example/loaders/LoaderActivity.java new file mode 100644 index 000000000000..49ef46f1c8de --- /dev/null +++ b/tests/AppResourcesLoaders/src/com/android/example/loaders/LoaderActivity.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.example.loaders; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +public class LoaderActivity extends Activity { + + @Override + protected void attachBaseContext(Context newBase) { + super.attachBaseContext(newBase); + final String loaderPresentOnAttach = + newBase.getResources().getString(R.string.loader_present); + if (loaderPresentOnAttach == null || !loaderPresentOnAttach.endsWith("true")) { + throw new AssertionError("Loader not present in attachBaseContext"); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + findViewById(R.id.btn_isolated_activity).setOnClickListener((v) -> { + startActivity(new Intent(this, LoaderActivityIsolated.class)); + }); + } +} diff --git a/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/MainActivity.java b/tests/AppResourcesLoaders/src/com/android/example/loaders/LoaderActivityIsolated.java index 9f1a0609d3f1..04550b980ea1 100644 --- a/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/MainActivity.java +++ b/tests/AppResourcesLoaders/src/com/android/example/loaders/LoaderActivityIsolated.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,24 +14,27 @@ * limitations under the License. */ -package com.android.tests.rollback.testapp; +package com.android.example.loaders; import android.app.Activity; +import android.content.Context; import android.os.Bundle; -/** - * A test app for testing apk rollback support. - */ -public class MainActivity extends Activity { +public class LoaderActivityIsolated extends Activity { + + @Override + protected void attachBaseContext(Context newBase) { + super.attachBaseContext(newBase); + final String loaderPresentOnAttach = + newBase.getResources().getString(R.string.loader_present); + if (loaderPresentOnAttach == null || !loaderPresentOnAttach.endsWith("true")) { + throw new AssertionError("Loader not present in attachBaseContext"); + } + } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - try { - new ProcessUserData().processUserData(this); - } catch (ProcessUserData.UserDataException e) { - throw new AssertionError("Failed to process app user data", e); - } + setContentView(R.layout.activity_isolated); } } diff --git a/tests/AppResourcesLoaders/src/com/android/example/loaders/LoadersApplication.java b/tests/AppResourcesLoaders/src/com/android/example/loaders/LoadersApplication.java new file mode 100644 index 000000000000..709c208f4174 --- /dev/null +++ b/tests/AppResourcesLoaders/src/com/android/example/loaders/LoadersApplication.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.example.loaders; + +import android.app.Application; +import android.content.res.Resources; +import android.content.res.loader.ResourcesLoader; +import android.content.res.loader.ResourcesProvider; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +public class LoadersApplication extends Application { + private static final String TAG = "LoadersApplication"; + private static final String LOADER_RESOURCES_APK = "AppResourcesLoaders_Overlay.apk"; + + @Override + public void onCreate() { + try { + final Resources resources = getResources(); + final ResourcesLoader loader = new ResourcesLoader(); + loader.addProvider(ResourcesProvider.loadFromApk(copyResource(LOADER_RESOURCES_APK))); + resources.addLoaders(loader); + } catch (IOException e) { + throw new IllegalStateException("Failed to load loader resources ", e); + } + } + + private ParcelFileDescriptor copyResource(String fileName) throws IOException { + final File apkFile = new File(getFilesDir(), fileName); + final InputStream is = getClassLoader().getResourceAsStream(LOADER_RESOURCES_APK); + final FileOutputStream os = new FileOutputStream(apkFile); + byte[] buffer = new byte[8192]; + int count; + while ((count = is.read(buffer)) != -1) { + os.write(buffer, 0, count); + } + return ParcelFileDescriptor.open(apkFile, ParcelFileDescriptor.MODE_READ_ONLY); + } +} diff --git a/tests/AutoVerify/app1/Android.bp b/tests/AutoVerify/app1/Android.bp new file mode 100644 index 000000000000..548519fa653b --- /dev/null +++ b/tests/AutoVerify/app1/Android.bp @@ -0,0 +1,11 @@ +android_app { + name: "AutoVerifyTest", + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + platform_apis: true, + min_sdk_version: "26", + target_sdk_version: "26", + optimize: { + enabled: false, + }, +} diff --git a/tests/RollbackTest/TestApp/Av2.xml b/tests/AutoVerify/app1/AndroidManifest.xml index f0e909feabf3..d9caad490d82 100644 --- a/tests/RollbackTest/TestApp/Av2.xml +++ b/tests/AutoVerify/app1/AndroidManifest.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2018 The Android Open Source Project +<!-- + Copyright (C) 2020 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,21 +16,28 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.tests.rollback.testapp.A" - android:versionCode="2" - android:versionName="2.0" > + package="com.android.test.autoverify" > + <uses-sdk android:targetSdkVersion="26" /> - <uses-sdk android:minSdkVersion="19" /> - - <application android:label="Rollback Test App A v2"> - <receiver android:name="com.android.tests.rollback.testapp.ProcessUserData" - android:exported="true" /> - <activity android:name="com.android.tests.rollback.testapp.MainActivity"> + <application + android:label="@string/app_name" > + <activity + android:name=".MainActivity" + android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> + + <intent-filter android:autoVerify="true"> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="http" /> + <data android:scheme="https" /> + <data android:host="explicit.example.com" /> + </intent-filter> </activity> </application> </manifest> diff --git a/tests/AutoVerify/app1/res/values/strings.xml b/tests/AutoVerify/app1/res/values/strings.xml new file mode 100644 index 000000000000..e234355041c6 --- /dev/null +++ b/tests/AutoVerify/app1/res/values/strings.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright 2020 The Android Open Source Project + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> + +<resources> + <!-- app icon label, do not translate --> + <string name="app_name" translatable="false">AutoVerify Test</string> +</resources> diff --git a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java b/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java new file mode 100644 index 000000000000..09ef47212622 --- /dev/null +++ b/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ diff --git a/tests/AutoVerify/app2/Android.bp b/tests/AutoVerify/app2/Android.bp new file mode 100644 index 000000000000..1c6c97bdf350 --- /dev/null +++ b/tests/AutoVerify/app2/Android.bp @@ -0,0 +1,11 @@ +android_app { + name: "AutoVerifyTest2", + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + platform_apis: true, + min_sdk_version: "26", + target_sdk_version: "26", + optimize: { + enabled: false, + }, +} diff --git a/tests/RollbackTest/TestApp/Av1.xml b/tests/AutoVerify/app2/AndroidManifest.xml index 63729fbaaf28..a00807883cfc 100644 --- a/tests/RollbackTest/TestApp/Av1.xml +++ b/tests/AutoVerify/app2/AndroidManifest.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2018 The Android Open Source Project +<!-- + Copyright (C) 2020 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,21 +16,29 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.tests.rollback.testapp.A" - android:versionCode="1" - android:versionName="1.0" > + package="com.android.test.autoverify" > + <uses-sdk android:targetSdkVersion="26" /> - <uses-sdk android:minSdkVersion="19" /> - - <application android:label="Rollback Test App A v1"> - <receiver android:name="com.android.tests.rollback.testapp.ProcessUserData" - android:exported="true" /> - <activity android:name="com.android.tests.rollback.testapp.MainActivity"> + <application + android:label="@string/app_name" > + <activity + android:name=".MainActivity" + android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> + + <intent-filter android:autoVerify="true"> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="http" /> + <data android:scheme="https" /> + <data android:host="explicit.example.com" /> + <data android:host="*.wildcard.tld" /> + </intent-filter> </activity> </application> </manifest> diff --git a/tests/AutoVerify/app2/res/values/strings.xml b/tests/AutoVerify/app2/res/values/strings.xml new file mode 100644 index 000000000000..e234355041c6 --- /dev/null +++ b/tests/AutoVerify/app2/res/values/strings.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright 2020 The Android Open Source Project + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> + +<resources> + <!-- app icon label, do not translate --> + <string name="app_name" translatable="false">AutoVerify Test</string> +</resources> diff --git a/tests/AutoVerify/app2/src/com/android/test/autoverify/MainActivity.java b/tests/AutoVerify/app2/src/com/android/test/autoverify/MainActivity.java new file mode 100644 index 000000000000..09ef47212622 --- /dev/null +++ b/tests/AutoVerify/app2/src/com/android/test/autoverify/MainActivity.java @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ diff --git a/tests/AutoVerify/app3/Android.bp b/tests/AutoVerify/app3/Android.bp new file mode 100644 index 000000000000..70a2b77d1000 --- /dev/null +++ b/tests/AutoVerify/app3/Android.bp @@ -0,0 +1,11 @@ +android_app { + name: "AutoVerifyTest3", + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + platform_apis: true, + min_sdk_version: "26", + target_sdk_version: "26", + optimize: { + enabled: false, + }, +} diff --git a/tests/RollbackTest/TestApp/Av3.xml b/tests/AutoVerify/app3/AndroidManifest.xml index 9725c9f7cf9e..efaabc9a38d3 100644 --- a/tests/RollbackTest/TestApp/Av3.xml +++ b/tests/AutoVerify/app3/AndroidManifest.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2018 The Android Open Source Project +<!-- + Copyright (C) 2020 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,21 +16,29 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.tests.rollback.testapp.A" - android:versionCode="3" - android:versionName="3.0" > + package="com.android.test.autoverify" > + <uses-sdk android:targetSdkVersion="26" /> - <uses-sdk android:minSdkVersion="19" /> - - <application android:label="Rollback Test App A v3"> - <receiver android:name="com.android.tests.rollback.testapp.ProcessUserData" - android:exported="true" /> - <activity android:name="com.android.tests.rollback.testapp.MainActivity"> + <application + android:label="@string/app_name" > + <activity + android:name=".MainActivity" + android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> + + <!-- does not request autoVerify --> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="http" /> + <data android:scheme="https" /> + <data android:host="explicit.example.com" /> + </intent-filter> </activity> </application> </manifest> diff --git a/tests/AutoVerify/app3/res/values/strings.xml b/tests/AutoVerify/app3/res/values/strings.xml new file mode 100644 index 000000000000..e234355041c6 --- /dev/null +++ b/tests/AutoVerify/app3/res/values/strings.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright 2020 The Android Open Source Project + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> + +<resources> + <!-- app icon label, do not translate --> + <string name="app_name" translatable="false">AutoVerify Test</string> +</resources> diff --git a/tests/AutoVerify/app3/src/com/android/test/autoverify/MainActivity.java b/tests/AutoVerify/app3/src/com/android/test/autoverify/MainActivity.java new file mode 100644 index 000000000000..09ef47212622 --- /dev/null +++ b/tests/AutoVerify/app3/src/com/android/test/autoverify/MainActivity.java @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ diff --git a/tests/AutoVerify/app4/Android.bp b/tests/AutoVerify/app4/Android.bp new file mode 100644 index 000000000000..fbdae1181a7a --- /dev/null +++ b/tests/AutoVerify/app4/Android.bp @@ -0,0 +1,11 @@ +android_app { + name: "AutoVerifyTest4", + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + platform_apis: true, + min_sdk_version: "26", + target_sdk_version: "26", + optimize: { + enabled: false, + }, +} diff --git a/tests/AutoVerify/app4/AndroidManifest.xml b/tests/AutoVerify/app4/AndroidManifest.xml new file mode 100644 index 000000000000..1c975f8336c9 --- /dev/null +++ b/tests/AutoVerify/app4/AndroidManifest.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.test.autoverify" > + + <uses-sdk android:targetSdkVersion="26" /> + + <application + android:label="@string/app_name" > + <activity + android:name=".MainActivity" + android:label="@string/app_name" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + + <!-- intentionally does not autoVerify --> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="http" /> + <data android:scheme="https" /> + <data android:host="explicit.example.com" /> + <data android:host="*.wildcard.tld" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/tests/AutoVerify/app4/res/values/strings.xml b/tests/AutoVerify/app4/res/values/strings.xml new file mode 100644 index 000000000000..e234355041c6 --- /dev/null +++ b/tests/AutoVerify/app4/res/values/strings.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright 2020 The Android Open Source Project + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> + +<resources> + <!-- app icon label, do not translate --> + <string name="app_name" translatable="false">AutoVerify Test</string> +</resources> diff --git a/tests/AutoVerify/app4/src/com/android/test/autoverify/MainActivity.java b/tests/AutoVerify/app4/src/com/android/test/autoverify/MainActivity.java new file mode 100644 index 000000000000..09ef47212622 --- /dev/null +++ b/tests/AutoVerify/app4/src/com/android/test/autoverify/MainActivity.java @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ diff --git a/tests/BlobStoreTestUtils/Android.bp b/tests/BlobStoreTestUtils/Android.bp new file mode 100644 index 000000000000..53d36389a52a --- /dev/null +++ b/tests/BlobStoreTestUtils/Android.bp @@ -0,0 +1,24 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +java_library { + name: "BlobStoreTestUtils", + srcs: ["src/**/*.java"], + static_libs: [ + "truth-prebuilt", + "androidx.test.uiautomator_uiautomator", + "androidx.test.ext.junit", + ], + sdk_version: "test_current", +}
\ No newline at end of file diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java new file mode 100644 index 000000000000..2df0024bdea9 --- /dev/null +++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java @@ -0,0 +1,256 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.utils.blob; + +import static com.android.utils.blob.Utils.BUFFER_SIZE_BYTES; +import static com.android.utils.blob.Utils.copy; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.blob.BlobHandle; +import android.app.blob.BlobStoreManager; +import android.content.Context; +import android.os.FileUtils; +import android.os.ParcelFileDescriptor; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.RandomAccessFile; +import java.security.MessageDigest; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +public class DummyBlobData { + private static final long DEFAULT_SIZE_BYTES = 10 * 1024L * 1024L; + + private final Random mRandom; + private final File mFile; + private final long mFileSize; + private final CharSequence mLabel; + private final long mExpiryDurationMs; + + byte[] mFileDigest; + long mExpiryTimeMs; + + private DummyBlobData(Builder builder) { + mRandom = new Random(builder.getRandomSeed()); + mFile = new File(builder.getContext().getFilesDir(), builder.getFileName()); + mFileSize = builder.getFileSize(); + mLabel = builder.getLabel(); + mExpiryDurationMs = builder.getExpiryDurationMs(); + } + + public static class Builder { + private final Context mContext; + private int mRandomSeed = 0; + private long mFileSize = DEFAULT_SIZE_BYTES; + private CharSequence mLabel = "Test label"; + private String mFileName = "blob_" + System.nanoTime(); + private long mExpiryDurationMs = TimeUnit.DAYS.toMillis(1); + + public Builder(Context context) { + mContext = context; + } + + public Context getContext() { + return mContext; + } + + public Builder setRandomSeed(int randomSeed) { + mRandomSeed = randomSeed; + return this; + } + + public int getRandomSeed() { + return mRandomSeed; + } + + public Builder setFileSize(long fileSize) { + mFileSize = fileSize; + return this; + } + + public long getFileSize() { + return mFileSize; + } + + public Builder setLabel(CharSequence label) { + mLabel = label; + return this; + } + + public CharSequence getLabel() { + return mLabel; + } + + public Builder setFileName(String fileName) { + mFileName = fileName; + return this; + } + + public String getFileName() { + return mFileName; + } + + public Builder setExpiryDurationMs(long durationMs) { + mExpiryDurationMs = durationMs; + return this; + } + + public long getExpiryDurationMs() { + return mExpiryDurationMs; + } + + public DummyBlobData build() { + return new DummyBlobData(this); + } + } + + public void prepare() throws Exception { + try (RandomAccessFile file = new RandomAccessFile(mFile, "rw")) { + writeRandomData(file, mFileSize); + } + mFileDigest = FileUtils.digest(mFile, "SHA-256"); + mExpiryTimeMs = System.currentTimeMillis() + mExpiryDurationMs; + } + + public BlobHandle getBlobHandle() throws Exception { + return BlobHandle.createWithSha256(mFileDigest, mLabel, + mExpiryTimeMs, "test_tag"); + } + + public long getFileSize() throws Exception { + return mFileSize; + } + + public long getExpiryTimeMillis() { + return mExpiryTimeMs; + } + + public void delete() { + mFile.delete(); + } + + public void writeToSession(BlobStoreManager.Session session) throws Exception { + writeToSession(session, 0, mFileSize); + } + + public void writeToSession(BlobStoreManager.Session session, + long offsetBytes, long lengthBytes) throws Exception { + try (FileInputStream in = new FileInputStream(mFile)) { + Utils.writeToSession(session, in, offsetBytes, lengthBytes, lengthBytes); + } + } + + public void writeToSession(BlobStoreManager.Session session, + long offsetBytes, long lengthBytes, long allocateBytes) throws Exception { + try (FileInputStream in = new FileInputStream(mFile)) { + Utils.writeToSession(session, in, offsetBytes, lengthBytes, allocateBytes); + } + } + + public void writeToFd(FileDescriptor fd, long offsetBytes, long lengthBytes) throws Exception { + try (FileInputStream in = new FileInputStream(mFile)) { + in.getChannel().position(offsetBytes); + try (FileOutputStream out = new FileOutputStream(fd)) { + copy(in, out, lengthBytes); + } + } + } + + public ParcelFileDescriptor openForRead() throws Exception { + return ParcelFileDescriptor.open(mFile, ParcelFileDescriptor.MODE_READ_ONLY); + } + + public void readFromSessionAndVerifyBytes(BlobStoreManager.Session session, + long offsetBytes, int lengthBytes) throws Exception { + final byte[] expectedBytes = new byte[lengthBytes]; + try (FileInputStream in = new FileInputStream(mFile)) { + read(in, expectedBytes, offsetBytes, lengthBytes); + } + + final byte[] actualBytes = new byte[lengthBytes]; + try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream( + session.openRead())) { + read(in, actualBytes, offsetBytes, lengthBytes); + } + + assertThat(actualBytes).isEqualTo(expectedBytes); + + } + + private void read(FileInputStream in, byte[] buffer, + long offsetBytes, int lengthBytes) throws Exception { + in.getChannel().position(offsetBytes); + in.read(buffer, 0, lengthBytes); + } + + public void readFromSessionAndVerifyDigest(BlobStoreManager.Session session) + throws Exception { + readFromSessionAndVerifyDigest(session, 0, mFile.length()); + } + + public void readFromSessionAndVerifyDigest(BlobStoreManager.Session session, + long offsetBytes, long lengthBytes) throws Exception { + final byte[] actualDigest; + try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream( + session.openRead())) { + actualDigest = createSha256Digest(in, offsetBytes, lengthBytes); + } + + assertThat(actualDigest).isEqualTo(mFileDigest); + } + + public void verifyBlob(ParcelFileDescriptor pfd) throws Exception { + final byte[] actualDigest; + try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) { + actualDigest = FileUtils.digest(in, "SHA-256"); + } + assertThat(actualDigest).isEqualTo(mFileDigest); + } + + private byte[] createSha256Digest(FileInputStream in, long offsetBytes, long lengthBytes) + throws Exception { + final MessageDigest digest = MessageDigest.getInstance("SHA-256"); + in.getChannel().position(offsetBytes); + final byte[] buffer = new byte[BUFFER_SIZE_BYTES]; + long bytesRead = 0; + while (bytesRead < lengthBytes) { + int toRead = (bytesRead + buffer.length <= lengthBytes) + ? buffer.length : (int) (lengthBytes - bytesRead); + toRead = in.read(buffer, 0, toRead); + digest.update(buffer, 0, toRead); + bytesRead += toRead; + } + return digest.digest(); + } + + private void writeRandomData(RandomAccessFile file, long fileSize) + throws Exception { + long bytesWritten = 0; + final byte[] buffer = new byte[BUFFER_SIZE_BYTES]; + while (bytesWritten < fileSize) { + mRandom.nextBytes(buffer); + final int toWrite = (bytesWritten + buffer.length <= fileSize) + ? buffer.length : (int) (fileSize - bytesWritten); + file.seek(bytesWritten); + file.write(buffer, 0, toWrite); + bytesWritten += toWrite; + } + } +} diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java new file mode 100644 index 000000000000..ec859955694c --- /dev/null +++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.utils.blob; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.blob.BlobHandle; +import android.app.blob.BlobStoreManager; +import android.app.blob.LeaseInfo; +import android.content.Context; +import android.content.res.Resources; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.uiautomator.UiDevice; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class Utils { + public static final String TAG = "BlobStoreTest"; + + public static final int BUFFER_SIZE_BYTES = 16 * 1024; + + public static final long KB_IN_BYTES = 1000; + public static final long MB_IN_BYTES = KB_IN_BYTES * 1000; + + public static void copy(InputStream in, OutputStream out, long lengthBytes) + throws IOException { + final byte[] buffer = new byte[BUFFER_SIZE_BYTES]; + long bytesWrittern = 0; + while (bytesWrittern < lengthBytes) { + final int toWrite = (bytesWrittern + buffer.length <= lengthBytes) + ? buffer.length : (int) (lengthBytes - bytesWrittern); + in.read(buffer, 0, toWrite); + out.write(buffer, 0, toWrite); + bytesWrittern += toWrite; + } + } + + public static void writeToSession(BlobStoreManager.Session session, ParcelFileDescriptor input, + long lengthBytes) throws IOException { + try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(input)) { + writeToSession(session, in, 0, lengthBytes, lengthBytes); + } + } + + public static void writeToSession(BlobStoreManager.Session session, FileInputStream in, + long offsetBytes, long lengthBytes, long allocateBytes) throws IOException { + in.getChannel().position(offsetBytes); + try (FileOutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream( + session.openWrite(offsetBytes, allocateBytes))) { + copy(in, out, lengthBytes); + } + } + + public static void assertLeasedBlobs(BlobStoreManager blobStoreManager, + BlobHandle... expectedBlobHandles) throws IOException { + assertThat(blobStoreManager.getLeasedBlobs()).containsExactly( + (Object[]) expectedBlobHandles); + } + + public static void assertNoLeasedBlobs(BlobStoreManager blobStoreManager) + throws IOException { + assertThat(blobStoreManager.getLeasedBlobs()).isEmpty(); + } + + public static void acquireLease(Context context, + BlobHandle blobHandle, CharSequence description) throws IOException { + final BlobStoreManager blobStoreManager = (BlobStoreManager) context.getSystemService( + Context.BLOB_STORE_SERVICE); + blobStoreManager.acquireLease(blobHandle, description); + + final LeaseInfo leaseInfo = blobStoreManager.getLeaseInfo(blobHandle); + assertLeaseInfo(leaseInfo, context.getPackageName(), 0, + Resources.ID_NULL, description); + } + + public static void acquireLease(Context context, + BlobHandle blobHandle, int descriptionResId) throws IOException { + final BlobStoreManager blobStoreManager = (BlobStoreManager) context.getSystemService( + Context.BLOB_STORE_SERVICE); + blobStoreManager.acquireLease(blobHandle, descriptionResId); + + final LeaseInfo leaseInfo = blobStoreManager.getLeaseInfo(blobHandle); + assertLeaseInfo(leaseInfo, context.getPackageName(), 0, + descriptionResId, context.getString(descriptionResId)); + } + + public static void acquireLease(Context context, + BlobHandle blobHandle, CharSequence description, + long expiryTimeMs) throws IOException { + final BlobStoreManager blobStoreManager = (BlobStoreManager) context.getSystemService( + Context.BLOB_STORE_SERVICE); + blobStoreManager.acquireLease(blobHandle, description, expiryTimeMs); + + final LeaseInfo leaseInfo = blobStoreManager.getLeaseInfo(blobHandle); + assertLeaseInfo(leaseInfo, context.getPackageName(), expiryTimeMs, + Resources.ID_NULL, description); + } + + public static void acquireLease(Context context, + BlobHandle blobHandle, int descriptionResId, + long expiryTimeMs) throws IOException { + final BlobStoreManager blobStoreManager = (BlobStoreManager) context.getSystemService( + Context.BLOB_STORE_SERVICE); + blobStoreManager.acquireLease(blobHandle, descriptionResId, expiryTimeMs); + + final LeaseInfo leaseInfo = blobStoreManager.getLeaseInfo(blobHandle); + assertLeaseInfo(leaseInfo, context.getPackageName(), expiryTimeMs, + descriptionResId, context.getString(descriptionResId)); + } + + public static void releaseLease(Context context, + BlobHandle blobHandle) throws IOException { + final BlobStoreManager blobStoreManager = (BlobStoreManager) context.getSystemService( + Context.BLOB_STORE_SERVICE); + blobStoreManager.releaseLease(blobHandle); + try { + assertThat(blobStoreManager.getLeaseInfo(blobHandle)).isNull(); + } catch (SecurityException e) { + // Expected, ignore + } + } + + private static void assertLeaseInfo(LeaseInfo leaseInfo, String packageName, + long expiryTimeMs, int descriptionResId, CharSequence description) { + assertThat(leaseInfo.getPackageName()).isEqualTo(packageName); + assertThat(leaseInfo.getExpiryTimeMillis()).isEqualTo(expiryTimeMs); + assertThat(leaseInfo.getDescriptionResId()).isEqualTo(descriptionResId); + assertThat(leaseInfo.getDescription()).isEqualTo(description); + } + + public static void triggerIdleMaintenance() throws IOException { + runShellCmd("cmd blob_store idle-maintenance"); + } + + public static String runShellCmd(String cmd) throws IOException { + final UiDevice uiDevice = UiDevice.getInstance( + InstrumentationRegistry.getInstrumentation()); + final String result = uiDevice.executeShellCommand(cmd).trim(); + Log.i(TAG, "Output of '" + cmd + "': '" + result + "'"); + return result; + } +} diff --git a/tests/BootImageProfileTest/Android.bp b/tests/BootImageProfileTest/Android.bp new file mode 100644 index 000000000000..1b097a8af0f9 --- /dev/null +++ b/tests/BootImageProfileTest/Android.bp @@ -0,0 +1,20 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +java_test_host { + name: "BootImageProfileTest", + srcs: ["src/**/*.java"], + libs: ["tradefed"], + test_suites: ["general-tests"], +} diff --git a/tests/BootImageProfileTest/AndroidTest.xml b/tests/BootImageProfileTest/AndroidTest.xml new file mode 100644 index 000000000000..7e97fa3a8ff1 --- /dev/null +++ b/tests/BootImageProfileTest/AndroidTest.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Config for BootImageProfileTest"> + <!-- do not use DeviceSetup#set-property because it reboots the device b/136200738. + furthermore the changes in /data/local.prop don't actually seem to get picked up. + --> + <target_preparer + class="com.android.tradefed.targetprep.DeviceSetup"> + <!-- we need this magic flag, otherwise it always reboots and breaks selinux --> + <option name="force-skip-system-props" value="true" /> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.DeviceCleaner"> + <option name="cleanup-action" value="REBOOT" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.HostTest" > + <option name="class" value="com.android.bootimageprofile.BootImageProfileTest" /> + </test> +</configuration> diff --git a/tests/BootImageProfileTest/OWNERS b/tests/BootImageProfileTest/OWNERS new file mode 100644 index 000000000000..657b3f2add2e --- /dev/null +++ b/tests/BootImageProfileTest/OWNERS @@ -0,0 +1,4 @@ +mathieuc@google.com +calin@google.com +yawanng@google.com +sehr@google.com diff --git a/tests/BootImageProfileTest/TEST_MAPPING b/tests/BootImageProfileTest/TEST_MAPPING new file mode 100644 index 000000000000..1b569f9455bf --- /dev/null +++ b/tests/BootImageProfileTest/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "BootImageProfileTest" + } + ] +} diff --git a/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java b/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java new file mode 100644 index 000000000000..4ecca2dc4c39 --- /dev/null +++ b/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.bootimageprofile; + +import static org.junit.Assert.assertTrue; + +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.IDeviceTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class BootImageProfileTest implements IDeviceTest { + private ITestDevice mTestDevice; + private static final String SYSTEM_SERVER_PROFILE = + "/data/misc/profiles/cur/0/android/primary.prof"; + private static final boolean USE_PHENOTYPE = false; + + @Override + public void setDevice(ITestDevice testDevice) { + mTestDevice = testDevice; + } + + @Override + public ITestDevice getDevice() { + return mTestDevice; + } + + private String getProperty(String property) throws Exception { + if (USE_PHENOTYPE) { + return mTestDevice.getProperty("persist.device_config.runtime_native_boot." + + property); + } else { + return mTestDevice.executeShellCommand("getprop dalvik.vm." + property).trim(); + } + } + + private String setProperty(String property, String value) throws Exception { + if (USE_PHENOTYPE) { + return mTestDevice.executeShellCommand( + "device_config put runtime_native_boot " + property + " " + value); + } else { + return mTestDevice.executeShellCommand( + "setprop dalvik.vm." + property + " " + value); + } + } + + /** + * Validate that the boot image profile properties are set. + */ + public void validateProperties() throws Exception { + String res = getProperty("profilebootclasspath"); + assertTrue("profile boot class path not enabled: " + res, "true".equals(res)); + res = getProperty("profilesystemserver"); + assertTrue("profile system server not enabled: " + res, "true".equals(res)); + } + + private boolean forceSaveProfile(String pkg) throws Exception { + String pid = mTestDevice.executeShellCommand("pidof " + pkg).trim(); + if (pid.length() == 0) { + // Not yet running. + return false; + } + String res = mTestDevice.executeShellCommand("kill -s SIGUSR1 " + pid).trim(); + return res.length() == 0; + } + + @Test + public void testSystemServerProfile() throws Exception { + final int numIterations = 30; + String res; + // Set properties and wait for them to be readable. + for (int i = 1; i <= numIterations; ++i) { + String pbcp = getProperty("profilebootclasspath"); + boolean profileBootClassPath = "true".equals(pbcp); + String pss = getProperty("profilesystemserver"); + boolean profileSystemServer = "true".equals(pss); + if (profileBootClassPath && profileSystemServer) { + break; + } + if (i == numIterations) { + assertTrue("profile system server not enabled: " + pss, profileSystemServer); + assertTrue("profile boot class path not enabled: " + pbcp, profileBootClassPath); + } + + setProperty("profilebootclasspath", "true"); + setProperty("profilesystemserver", "true"); + Thread.sleep(1000); + } + + // Restart shell and wait for system boot. + res = mTestDevice.executeShellCommand("stop"); + assertTrue("stop shell: " + res, res.length() == 0); + res = mTestDevice.executeShellCommand("start"); + assertTrue("start shell: " + res, res.length() == 0); + for (int i = 1; i <= numIterations; ++i) { + String pbcp = getProperty("profilebootclasspath"); + boolean profileBootClassPath = "true".equals(pbcp); + String pss = getProperty("profilesystemserver"); + boolean profileSystemServer = "true".equals(pss); + if (profileBootClassPath && profileSystemServer) { + break; + } + if (i == numIterations) { + assertTrue("profile system server not enabled: " + pss, profileSystemServer); + assertTrue("profile boot class path not enabled: " + pbcp, profileBootClassPath); + } + Thread.sleep(1000); + } + + // Trunacte the profile before force it to be saved to prevent previous profiles + // causing the test to pass. + res = mTestDevice.executeShellCommand("truncate -s 0 " + SYSTEM_SERVER_PROFILE).trim(); + assertTrue(res, res.length() == 0); + // Wait up to 20 seconds for the profile to be saved. + for (int i = 1; i <= numIterations; ++i) { + // Force save the profile since we truncated it. + if (forceSaveProfile("system_server")) { + // Might fail if system server is not yet running. + String s = mTestDevice.executeShellCommand( + "wc -c <" + SYSTEM_SERVER_PROFILE).trim(); + if ("0".equals(s)) { + Thread.sleep(1000); + continue; + } + } + + // In case the profile is partially saved, wait an extra second. + Thread.sleep(1000); + + // Validate that properties are still set. + validateProperties(); + + // Validate that the profile is non empty. + res = mTestDevice.executeShellCommand("profman --dump-only --profile-file=" + + SYSTEM_SERVER_PROFILE); + boolean sawFramework = false; + boolean sawServices = false; + for (String line : res.split("\n")) { + if (line.contains("framework.jar")) { + sawFramework = true; + } else if (line.contains("framework-minus-apex.jar")) { + sawFramework = true; + } else if (line.contains("services.jar")) { + sawServices = true; + } + } + if (i == numIterations) { + // Only assert for last iteration since there are race conditions where the package + // manager might not be started whewn the profile saves. + assertTrue("Did not see framework.jar in " + res, sawFramework); + assertTrue("Did not see services.jar in " + res, sawServices); + } + + // Test the profile contents contain common methods for core-oj that would normally be + // AOT compiled. Also test that services.jar has PackageManagerService.<init> since the + // package manager service should always be created during boot. + res = mTestDevice.executeShellCommand( + "profman --dump-classes-and-methods --profile-file=" + + SYSTEM_SERVER_PROFILE + " --apk=/apex/com.android.art/javalib/core-oj.jar" + + " --apk=/system/framework/services.jar"); + boolean sawObjectInit = false; + boolean sawPmInit = false; + for (String line : res.split("\n")) { + if (line.contains("Ljava/lang/Object;-><init>()V")) { + sawObjectInit = true; + } else if (line.contains("Lcom/android/server/pm/PackageManagerService;-><init>")) { + sawPmInit = true; + } + } + if (i == numIterations) { + assertTrue("Did not see Object.<init> in " + res, sawObjectInit); + assertTrue("Did not see PackageManagerService.<init> in " + res, sawPmInit); + } + + if (sawFramework && sawServices && sawObjectInit && sawPmInit) { + break; // Asserts passed, exit. + } + } + } +} diff --git a/tests/CanvasCompare/res/drawable/sunset1.jpg b/tests/CanvasCompare/res/drawable/sunset1.jpg Binary files differindex 92851f3df924..3b4e056b70d0 100644 --- a/tests/CanvasCompare/res/drawable/sunset1.jpg +++ b/tests/CanvasCompare/res/drawable/sunset1.jpg diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rs b/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rscript index 0a1742ef3867..0a1742ef3867 100644 --- a/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rs +++ b/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rscript diff --git a/tests/Codegen/Android.bp b/tests/Codegen/Android.bp new file mode 100644 index 000000000000..966c5602959c --- /dev/null +++ b/tests/Codegen/Android.bp @@ -0,0 +1,25 @@ +android_test { + name: "CodegenTests", + srcs: [ + "**/*.java", + ], + + platform_apis: true, + test_suites: ["device-tests"], + certificate: "platform", + + optimize: { + enabled: false, + }, + + plugins: [ + "staledataclass-annotation-processor", + ], + static_libs: [ + "junit", + "hamcrest", + "hamcrest-library", + "androidx.test.runner", + "androidx.test.rules", + ], +} diff --git a/tests/Codegen/AndroidManifest.xml b/tests/Codegen/AndroidManifest.xml new file mode 100644 index 000000000000..2f1855035cd8 --- /dev/null +++ b/tests/Codegen/AndroidManifest.xml @@ -0,0 +1,26 @@ +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.codegentest"> + + <application/> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.codegentest" + android:label="Codegen test" /> +</manifest> diff --git a/tests/RollbackTest/TestApp/res_v2/values-anydpi/values.xml b/tests/Codegen/AndroidTest.xml index 9a1aa7fd8461..4dbbc5556c64 100644 --- a/tests/RollbackTest/TestApp/res_v2/values-anydpi/values.xml +++ b/tests/Codegen/AndroidTest.xml @@ -13,7 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. --> +<configuration description="Runs Codegen Tests."> -<resources> - <integer name="split_version">2</integer> -</resources> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.codegentest" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/tests/Codegen/OWNERS b/tests/Codegen/OWNERS new file mode 100644 index 000000000000..da723b3b67da --- /dev/null +++ b/tests/Codegen/OWNERS @@ -0,0 +1 @@ +eugenesusla@google.com
\ No newline at end of file diff --git a/tests/Codegen/runTest.sh b/tests/Codegen/runTest.sh new file mode 100755 index 000000000000..31ab6d2ba46a --- /dev/null +++ b/tests/Codegen/runTest.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +if [[ "$0" = *"/Codegen/runTest.sh" ]]; then + #running in subshell - print code to eval and exit + echo "source $0" +else + function header_and_eval() { + printf "\n[ $* ]\n" 1>&2 + eval "$@" + return $? + } + + header_and_eval m -j16 codegen_cli && \ + header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java && \ + header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java && \ + header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java && \ + header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java && \ + header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java && \ + header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java && \ + header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java && \ + ( + cd $ANDROID_BUILD_TOP && + header_and_eval mmma -j16 frameworks/base/tests/Codegen && \ + header_and_eval adb install -r -t "$(find $ANDROID_TARGET_OUT_TESTCASES -name 'CodegenTests.apk')" && \ + # header_and_eval adb shell am set-debug-app -w com.android.codegentest && \ + header_and_eval adb shell am instrument -w -e package com.android.codegentest com.android.codegentest/androidx.test.runner.AndroidJUnitRunner + ) + + exitCode=$? + + # header_and_eval adb shell am clear-debug-app + + return $exitCode +fi
\ No newline at end of file diff --git a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.aidl b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.aidl new file mode 100644 index 000000000000..ab62c83fc1b9 --- /dev/null +++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.codegentest; + +parcelable HierrarchicalDataClassBase; diff --git a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java new file mode 100644 index 000000000000..475305eb83a2 --- /dev/null +++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.codegentest; + +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +/** + * @see HierrarchicalDataClassChild + */ +@DataClass( + genConstructor = false, + genSetters = true) +public class HierrarchicalDataClassBase implements Parcelable { + + private int mBaseData; + + + + // Code below generated by codegen v1.0.15. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + public int getBaseData() { + return mBaseData; + } + + @DataClass.Generated.Member + public HierrarchicalDataClassBase setBaseData( int value) { + mBaseData = value; + return this; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeInt(mBaseData); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + protected HierrarchicalDataClassBase(@android.annotation.NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + int baseData = in.readInt(); + + this.mBaseData = baseData; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @android.annotation.NonNull Parcelable.Creator<HierrarchicalDataClassBase> CREATOR + = new Parcelable.Creator<HierrarchicalDataClassBase>() { + @Override + public HierrarchicalDataClassBase[] newArray(int size) { + return new HierrarchicalDataClassBase[size]; + } + + @Override + public HierrarchicalDataClassBase createFromParcel(@android.annotation.NonNull android.os.Parcel in) { + return new HierrarchicalDataClassBase(in); + } + }; + + @DataClass.Generated( + time = 1582685650576L, + codegenVersion = "1.0.15", + sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java", + inputSignatures = "private int mBaseData\nclass HierrarchicalDataClassBase extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genSetters=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.aidl b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.aidl new file mode 100644 index 000000000000..a0997222b0af --- /dev/null +++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.codegentest; + +parcelable HierrarchicalDataClassChild; diff --git a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java new file mode 100644 index 000000000000..150b324d1a30 --- /dev/null +++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.codegentest; + +import android.annotation.NonNull; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +/** + * An example of data classes that extend one another. + * + * Note that some features like constructor generation might not work well due to lack of + * information about the superclass when generating code for subclass. + * + * It is recommended to avoid inheritance in favor of composition for new data classes, + * particularly parcelable ones. + * + * However for legacy classes or where inheritance is desired for allocation efficiency, + * you can either use a technique from this example, opting for mutability/setters, or just write + * constructors by hand. + * + * @see HierrarchicalDataClassBase + */ +@DataClass( + genParcelable = true, + genConstructor = false, + genSetters = true) +public class HierrarchicalDataClassChild extends HierrarchicalDataClassBase { + + private @NonNull String mChildData; + + + + // Code below generated by codegen v1.0.15. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + public @NonNull String getChildData() { + return mChildData; + } + + @DataClass.Generated.Member + public HierrarchicalDataClassChild setChildData(@NonNull String value) { + mChildData = value; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mChildData); + return this; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + super.writeToParcel(dest, flags); + + dest.writeString(mChildData); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + protected HierrarchicalDataClassChild(@NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + super(in); + + String childData = in.readString(); + + this.mChildData = childData; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mChildData); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<HierrarchicalDataClassChild> CREATOR + = new Parcelable.Creator<HierrarchicalDataClassChild>() { + @Override + public HierrarchicalDataClassChild[] newArray(int size) { + return new HierrarchicalDataClassChild[size]; + } + + @Override + public HierrarchicalDataClassChild createFromParcel(@NonNull android.os.Parcel in) { + return new HierrarchicalDataClassChild(in); + } + }; + + @DataClass.Generated( + time = 1582685651560L, + codegenVersion = "1.0.15", + sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java", + inputSignatures = "private @android.annotation.NonNull java.lang.String mChildData\nclass HierrarchicalDataClassChild extends com.android.codegentest.HierrarchicalDataClassBase implements []\n@com.android.internal.util.DataClass(genParcelable=true, genConstructor=false, genSetters=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/tests/Codegen/src/com/android/codegentest/MyDateParcelling.java b/tests/Codegen/src/com/android/codegentest/MyDateParcelling.java new file mode 100644 index 000000000000..4faeb8ee8893 --- /dev/null +++ b/tests/Codegen/src/com/android/codegentest/MyDateParcelling.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.codegentest; + +import android.os.Parcel; + +import com.android.internal.util.Parcelling; + +import java.util.Date; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Sample {@link Parcelling} implementation for {@link Date}. + * + * See {@link SampleDataClass#mDate} for usage. + * See {@link SampleDataClass#writeToParcel} + {@link SampleDataClass#sParcellingForDate} + * for resulting generated code. + * + * Ignore {@link #sInstanceCount} - used for testing. + */ +public class MyDateParcelling implements Parcelling<Date> { + + static AtomicInteger sInstanceCount = new AtomicInteger(0); + + public MyDateParcelling() { + sInstanceCount.getAndIncrement(); + } + + @Override + public void parcel(Date item, Parcel dest, int parcelFlags) { + dest.writeLong(item.getTime()); + } + + @Override + public Date unparcel(Parcel source) { + return new Date(source.readLong()); + } +} diff --git a/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java b/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java new file mode 100644 index 000000000000..30871566c269 --- /dev/null +++ b/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java @@ -0,0 +1,426 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.codegentest; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.ArrayMap; +import android.util.SparseArray; +import android.util.SparseIntArray; + +import com.android.internal.util.AnnotationValidations; +import com.android.internal.util.DataClass; + +import java.util.List; +import java.util.Map; + +/** + * Additional test for various parcelling corner-cases. + */ +@DataClass( + genBuilder = true, + genAidl = false, + genToString = true) +public class ParcelAllTheThingsDataClass implements Parcelable { + + @NonNull String[] mStringArray = null; + @NonNull int[] mIntArray = null; + @NonNull List<String> mStringList = null; + + @NonNull Map<String, SampleWithCustomBuilder> mMap = null; + @NonNull Map<String, String> mStringMap = null; + + @NonNull SparseArray<SampleWithCustomBuilder> mSparseArray = null; + @NonNull SparseIntArray mSparseIntArray = null; + + @SuppressWarnings({"WeakerAccess"}) + @Nullable Boolean mNullableBoolean = null; + + + + + // Code below generated by codegen v1.0.15. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + /* package-private */ ParcelAllTheThingsDataClass( + @NonNull String[] stringArray, + @NonNull int[] intArray, + @NonNull List<String> stringList, + @NonNull Map<String,SampleWithCustomBuilder> map, + @NonNull Map<String,String> stringMap, + @NonNull SparseArray<SampleWithCustomBuilder> sparseArray, + @NonNull SparseIntArray sparseIntArray, + @SuppressWarnings({ "WeakerAccess" }) @Nullable Boolean nullableBoolean) { + this.mStringArray = stringArray; + AnnotationValidations.validate( + NonNull.class, null, mStringArray); + this.mIntArray = intArray; + AnnotationValidations.validate( + NonNull.class, null, mIntArray); + this.mStringList = stringList; + AnnotationValidations.validate( + NonNull.class, null, mStringList); + this.mMap = map; + AnnotationValidations.validate( + NonNull.class, null, mMap); + this.mStringMap = stringMap; + AnnotationValidations.validate( + NonNull.class, null, mStringMap); + this.mSparseArray = sparseArray; + AnnotationValidations.validate( + NonNull.class, null, mSparseArray); + this.mSparseIntArray = sparseIntArray; + AnnotationValidations.validate( + NonNull.class, null, mSparseIntArray); + this.mNullableBoolean = nullableBoolean; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public @NonNull String[] getStringArray() { + return mStringArray; + } + + @DataClass.Generated.Member + public @NonNull int[] getIntArray() { + return mIntArray; + } + + @DataClass.Generated.Member + public @NonNull List<String> getStringList() { + return mStringList; + } + + @DataClass.Generated.Member + public @NonNull Map<String,SampleWithCustomBuilder> getMap() { + return mMap; + } + + @DataClass.Generated.Member + public @NonNull Map<String,String> getStringMap() { + return mStringMap; + } + + @DataClass.Generated.Member + public @NonNull SparseArray<SampleWithCustomBuilder> getSparseArray() { + return mSparseArray; + } + + @DataClass.Generated.Member + public @NonNull SparseIntArray getSparseIntArray() { + return mSparseIntArray; + } + + @DataClass.Generated.Member + public @SuppressWarnings({ "WeakerAccess" }) @Nullable Boolean getNullableBoolean() { + return mNullableBoolean; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "ParcelAllTheThingsDataClass { " + + "stringArray = " + java.util.Arrays.toString(mStringArray) + ", " + + "intArray = " + java.util.Arrays.toString(mIntArray) + ", " + + "stringList = " + mStringList + ", " + + "map = " + mMap + ", " + + "stringMap = " + mStringMap + ", " + + "sparseArray = " + mSparseArray + ", " + + "sparseIntArray = " + mSparseIntArray + ", " + + "nullableBoolean = " + mNullableBoolean + + " }"; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + int flg = 0; + if (mNullableBoolean != null) flg |= 0x80; + dest.writeInt(flg); + dest.writeStringArray(mStringArray); + dest.writeIntArray(mIntArray); + dest.writeStringList(mStringList); + dest.writeMap(mMap); + dest.writeMap(mStringMap); + dest.writeSparseArray(mSparseArray); + dest.writeSparseIntArray(mSparseIntArray); + if (mNullableBoolean != null) dest.writeBoolean(mNullableBoolean); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + protected ParcelAllTheThingsDataClass(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + int flg = in.readInt(); + String[] stringArray = in.createStringArray(); + int[] intArray = in.createIntArray(); + List<String> stringList = new java.util.ArrayList<>(); + in.readStringList(stringList); + Map<String,SampleWithCustomBuilder> map = new java.util.LinkedHashMap<>(); + in.readMap(map, SampleWithCustomBuilder.class.getClassLoader()); + Map<String,String> stringMap = new java.util.LinkedHashMap<>(); + in.readMap(stringMap, String.class.getClassLoader()); + SparseArray<SampleWithCustomBuilder> sparseArray = (SparseArray) in.readSparseArray(SampleWithCustomBuilder.class.getClassLoader()); + SparseIntArray sparseIntArray = (SparseIntArray) in.readSparseIntArray(); + Boolean nullableBoolean = (flg & 0x80) == 0 ? null : (Boolean) in.readBoolean(); + + this.mStringArray = stringArray; + AnnotationValidations.validate( + NonNull.class, null, mStringArray); + this.mIntArray = intArray; + AnnotationValidations.validate( + NonNull.class, null, mIntArray); + this.mStringList = stringList; + AnnotationValidations.validate( + NonNull.class, null, mStringList); + this.mMap = map; + AnnotationValidations.validate( + NonNull.class, null, mMap); + this.mStringMap = stringMap; + AnnotationValidations.validate( + NonNull.class, null, mStringMap); + this.mSparseArray = sparseArray; + AnnotationValidations.validate( + NonNull.class, null, mSparseArray); + this.mSparseIntArray = sparseIntArray; + AnnotationValidations.validate( + NonNull.class, null, mSparseIntArray); + this.mNullableBoolean = nullableBoolean; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<ParcelAllTheThingsDataClass> CREATOR + = new Parcelable.Creator<ParcelAllTheThingsDataClass>() { + @Override + public ParcelAllTheThingsDataClass[] newArray(int size) { + return new ParcelAllTheThingsDataClass[size]; + } + + @Override + public ParcelAllTheThingsDataClass createFromParcel(@NonNull Parcel in) { + return new ParcelAllTheThingsDataClass(in); + } + }; + + /** + * A builder for {@link ParcelAllTheThingsDataClass} + */ + @SuppressWarnings("WeakerAccess") + @DataClass.Generated.Member + public static class Builder { + + private @NonNull String[] mStringArray; + private @NonNull int[] mIntArray; + private @NonNull List<String> mStringList; + private @NonNull Map<String,SampleWithCustomBuilder> mMap; + private @NonNull Map<String,String> mStringMap; + private @NonNull SparseArray<SampleWithCustomBuilder> mSparseArray; + private @NonNull SparseIntArray mSparseIntArray; + private @SuppressWarnings({ "WeakerAccess" }) @Nullable Boolean mNullableBoolean; + + private long mBuilderFieldsSet = 0L; + + public Builder() { + } + + @DataClass.Generated.Member + public @NonNull Builder setStringArray(@NonNull String... value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mStringArray = value; + return this; + } + + @DataClass.Generated.Member + public @NonNull Builder setIntArray(@NonNull int... value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mIntArray = value; + return this; + } + + @DataClass.Generated.Member + public @NonNull Builder setStringList(@NonNull List<String> value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mStringList = value; + return this; + } + + /** @see #setStringList */ + @DataClass.Generated.Member + public @NonNull Builder addStringList(@NonNull String value) { + // You can refine this method's name by providing item's singular name, e.g.: + // @DataClass.PluralOf("item")) mItems = ... + + if (mStringList == null) setStringList(new java.util.ArrayList<>()); + mStringList.add(value); + return this; + } + + @DataClass.Generated.Member + public @NonNull Builder setMap(@NonNull Map<String,SampleWithCustomBuilder> value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x8; + mMap = value; + return this; + } + + /** @see #setMap */ + @DataClass.Generated.Member + public @NonNull Builder addMap(@NonNull String key, @NonNull SampleWithCustomBuilder value) { + // You can refine this method's name by providing item's singular name, e.g.: + // @DataClass.PluralOf("item")) mItems = ... + + if (mMap == null) setMap(new java.util.LinkedHashMap()); + mMap.put(key, value); + return this; + } + + @DataClass.Generated.Member + public @NonNull Builder setStringMap(@NonNull Map<String,String> value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x10; + mStringMap = value; + return this; + } + + /** @see #setStringMap */ + @DataClass.Generated.Member + public @NonNull Builder addStringMap(@NonNull String key, @NonNull String value) { + // You can refine this method's name by providing item's singular name, e.g.: + // @DataClass.PluralOf("item")) mItems = ... + + if (mStringMap == null) setStringMap(new java.util.LinkedHashMap()); + mStringMap.put(key, value); + return this; + } + + @DataClass.Generated.Member + public @NonNull Builder setSparseArray(@NonNull SparseArray<SampleWithCustomBuilder> value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x20; + mSparseArray = value; + return this; + } + + @DataClass.Generated.Member + public @NonNull Builder setSparseIntArray(@NonNull SparseIntArray value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x40; + mSparseIntArray = value; + return this; + } + + @DataClass.Generated.Member + public @NonNull Builder setNullableBoolean(@SuppressWarnings({ "WeakerAccess" }) @NonNull Boolean value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x80; + mNullableBoolean = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull ParcelAllTheThingsDataClass build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x100; // Mark builder used + + if ((mBuilderFieldsSet & 0x1) == 0) { + mStringArray = null; + } + if ((mBuilderFieldsSet & 0x2) == 0) { + mIntArray = null; + } + if ((mBuilderFieldsSet & 0x4) == 0) { + mStringList = null; + } + if ((mBuilderFieldsSet & 0x8) == 0) { + mMap = null; + } + if ((mBuilderFieldsSet & 0x10) == 0) { + mStringMap = null; + } + if ((mBuilderFieldsSet & 0x20) == 0) { + mSparseArray = null; + } + if ((mBuilderFieldsSet & 0x40) == 0) { + mSparseIntArray = null; + } + if ((mBuilderFieldsSet & 0x80) == 0) { + mNullableBoolean = null; + } + ParcelAllTheThingsDataClass o = new ParcelAllTheThingsDataClass( + mStringArray, + mIntArray, + mStringList, + mMap, + mStringMap, + mSparseArray, + mSparseIntArray, + mNullableBoolean); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x100) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } + + @DataClass.Generated( + time = 1582685649678L, + codegenVersion = "1.0.15", + sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java", + inputSignatures = " @android.annotation.NonNull java.lang.String[] mStringArray\n @android.annotation.NonNull int[] mIntArray\n @android.annotation.NonNull java.util.List<java.lang.String> mStringList\n @android.annotation.NonNull java.util.Map<java.lang.String,com.android.codegentest.SampleWithCustomBuilder> mMap\n @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.String> mStringMap\n @android.annotation.NonNull android.util.SparseArray<com.android.codegentest.SampleWithCustomBuilder> mSparseArray\n @android.annotation.NonNull android.util.SparseIntArray mSparseIntArray\n @java.lang.SuppressWarnings({\"WeakerAccess\"}) @android.annotation.Nullable java.lang.Boolean mNullableBoolean\nclass ParcelAllTheThingsDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClass.aidl b/tests/Codegen/src/com/android/codegentest/SampleDataClass.aidl new file mode 100644 index 000000000000..f14d47cde13f --- /dev/null +++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.aidl @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.codegentest; + +parcelable SampleDataClass; diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java new file mode 100644 index 000000000000..8d421bfa492a --- /dev/null +++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java @@ -0,0 +1,1886 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.codegentest; + +import android.annotation.FloatRange; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.Size; +import android.annotation.StringDef; +import android.annotation.StringRes; +import android.annotation.UserIdInt; +import android.net.LinkAddress; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.accessibility.AccessibilityNodeInfo; + +import com.android.internal.util.AnnotationValidations; +import com.android.internal.util.DataClass; +import com.android.internal.util.DataClass.Each; +import com.android.internal.util.Parcelling; +import com.android.internal.util.Preconditions; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.regex.Pattern; + +/** + * Sample data class, showing off various code generation features. + * + * See javadoc on non-generated code for the explanation of the various features. + * + * See {@link SampleDataClassTest} for various invariants the generated code is expected to hold. + */ +@DataClass( +// genParcelable = true, // implied by `implements Parcelable` +// genAidl = true, // implied by `implements Parcelable` +// genGetters = true, // on by default +// genConstDefs = true, // implied by presence of constants with common prefix + genBuilder = true, // on by default if optional fields present, but suppressed by + // genConstructor + genConstructor = true, // on by default but normally suppressed by genBuilder + genEqualsHashCode = true, + genToString = true, + genForEachField = true, + genSetters = true +) +public final class SampleDataClass implements Parcelable { + + /** + * For any group of {@link int} or {@link String} constants like these, a corresponding + * {@link IntDef}/{@link StringDef} will get generated, with name based on common prefix + * by default. + * + * When {@link #SampleDataClass constructing} an instance, fields annotated with these + * annotations get automatically validated, with only provided constants being a valid value. + * + * @see StateName, the generated {@link StringDef} + * @see #mStateName annotated with {@link StateName} + */ + public static final String STATE_NAME_UNDEFINED = "?"; + public static final String STATE_NAME_ON = "on"; + public static final String STATE_NAME_OFF = "off"; + + /** + * Additionally, for any generated {@link IntDef} a corresponding static + * *ToString method will be also generated, and used in {@link #toString()}. + * + * @see #stateToString(int) + * @see #toString() + * @see State + */ + public static final int STATE_UNDEFINED = -1; + public static final int STATE_ON = 1; + public static final int STATE_OFF = 0; + + /** + * {@link IntDef}s with values specified in hex("0x...") are considered to be + * {@link IntDef#flag flags}, while ones specified with regular int literals are considered + * not to be flags. + * + * This affects their string representation, e.g. see the difference in + * {@link #requestFlagsToString} vs {@link #stateToString}. + * + * This also affects the validation logic when {@link #SampleDataClass constructing} + * an instance, with any flag combination("|") being valid. + * + * You can customize the name of the generated {@link IntDef}/{@link StringDef} annotation + * by annotating each constant with the desired name before running the generation. + * + * Here the annotation is named {@link RequestFlags} instead of the default {@code Flags}. + */ + public static final @RequestFlags int FLAG_MANUAL_REQUEST = 0x1; + public static final @RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST = 0x2; + public static final @RequestFlags int FLAG_AUGMENTED_REQUEST = 0x80000000; + + + /** + * Any property javadoc should go onto the field, and will be copied where appropriate, + * including getters, constructor parameters, builder setters, etc. + * + * <p> + * This allows to avoid the burden of maintaining copies of the same documentation + * pieces in multiple places for each field. + */ + private int mNum; + /** + * Various javadoc features should work as expected when copied, e.g {@code code}, + * {@link #mName links}, <a href="https://google.com">html links</a>, etc. + * + * @see #mNum2 ..and so should blocks at the bottom, e.g. {@code @see} blocks. + */ + private int mNum2; + /** + * {@code @hide} javadoc annotation is also propagated, which can be used to adjust the + * desired public API surface. + * + * @see #getNum4() is hidden + * @see Builder#setNum4(int) also hidden + * @hide + */ + private int mNum4; + + /** + * {@link Nullable} or {@link NonNull} annotation is required on all non-primitive fields. + */ + private @Nullable String mName; + /** + * Fields with default value expressions ("mFoo = ...") are optional, and are automatically + * initialized to the provided default expression, unless explicitly set. + * + * When using a {@link Builder} optional fields are passed via a {@link Builder#setName2 setter} + * while mandatory fields are passed via {@link Builder#Builder constructor}. + */ + private @NonNull String mName2 = "Bob"; + /** + * Alternatively, when default value computation is expensive, + * {@link #defaultName4 defaultFieldName()} can be defined to compute the default value. + */ + private @NonNull String mName4; + private static String defaultName4() { + // Expensive computation + return "Bob4"; + } + + /** + * For parcelling, any field type supported by {@link Parcel} is supported out of the box. + * E.g. {@link Parcelable} subclasses, {@link String}, {@link int}, {@link boolean}, etc. + */ + private @Nullable AccessibilityNodeInfo mOtherParcelable = null; + /** + * Additionally, support for parcelling other types can be added by implementing a + * {@link Parcelling}, and referencing it in the {@link DataClass.ParcelWith} field annotation. + * + * @see MyDateParcelling an example {@link Parcelling} implementation + */ + @DataClass.ParcelWith(MyDateParcelling.class) + private @NonNull Date mDate = new Date(42 * 42); + /** + * If a {@link Parcelling} is fairly common, consider putting it in {@link Parcelling.BuiltIn} + * to encourage its reuse. + */ + @DataClass.ParcelWith(Parcelling.BuiltIn.ForPattern.class) + private @NonNull Pattern mPattern = Pattern.compile(""); + + /** + * For lists, when using a {@link Builder}, other than a regular + * {@link Builder#setLinkAddresses2(List) setter}, and additional + * {@link Builder#addLinkAddresses2(LinkAddress) add} method is generated for convenience. + */ + private @NonNull List<LinkAddress> mLinkAddresses2 = new ArrayList<>(); + /** + * For aesthetics, you may want to consider providing a singular version of the plural field + * name, which would be used for the {@link #mLinkAddresses2 above mentioned} "add" method. + * + * @see Builder#addLinkAddress(LinkAddress) + */ + @DataClass.PluralOf("linkAddress") + private @NonNull ArrayList<LinkAddress> mLinkAddresses = new ArrayList<>(); + /** + * For array fields, when using a {@link Builder}, vararg argument format is used for + * convenience. + * + * @see Builder#setLinkAddresses4(LinkAddress...) + */ + private @Nullable LinkAddress[] mLinkAddresses4 = null; + + /** + * {@link IntDef}/{@link StringDef}-annotated fields propagate the annotation to + * getter/constructor/setter/builder parameters, making for a nicer api. + * + * @see #getStateName + * @see Builder#setStateName + */ + private @StateName @NonNull String mStateName = STATE_NAME_UNDEFINED; + /** + * Fields annotated with {@link IntDef} annotations also get a proper {@link #toString()} value. + */ + private @RequestFlags int mFlags; + /** + * Above is true for both {@link IntDef#flag flags} and enum-like {@link IntDef}s + */ + private @State int mState = STATE_UNDEFINED; + + + /** + * Making a field public will suppress getter generation in favor of accessing it directly. + */ + public @NonNull CharSequence charSeq = ""; + /** + * Final fields suppress generating a setter (when setters are requested). + */ + private final @Nullable LinkAddress[] mLinkAddresses5; + /** + * Transient fields are completely ignored and can be used for caching. + */ + private transient LinkAddress[] mLinkAddresses6; + /** + * When using transient fields for caching it's often also a good idea to initialize them + * lazily. + * + * You can declare a special method like {@link #lazyInitTmpStorage()}, to let the + * {@link #getTmpStorage getter} lazily-initialize the value on demand. + */ + transient int[] mTmpStorage; + private int[] lazyInitTmpStorage() { + return new int[100]; + } + + /** + * Fields with certain annotations are automatically validated in constructor + * + * You can see overloads in {@link AnnotationValidations} for a list of currently + * supported ones. + * + * You can also extend support to your custom annotations by creating another corresponding + * overloads like + * {@link AnnotationValidations#validate(Class, UserIdInt, int)}. + * + * @see #SampleDataClass + */ + private @StringRes int mStringRes = 0; + /** + * Validation annotations may also have parameters. + * + * Parameter values will be supplied to validation method as name-value pairs. + * + * @see AnnotationValidations#validate(Class, Size, int, String, int, String, int) + */ + private @android.annotation.IntRange(from = 0, to = 6) int mDayOfWeek = 3; + /** + * Unnamed validation annotation parameter gets supplied to the validating method named as + * "value". + * + * Validation annotations following {@link Each} annotation, will be applied for each + * array/collection element instead. + * + * @see AnnotationValidations#validate(Class, Size, int, String, int) + */ + @Size(2) + @NonNull + @Each @FloatRange(from = 0f) + private float[] mCoords = new float[] {0f, 0f}; + + + /** + * Manually declaring any method that would otherwise be generated suppresses its generation, + * allowing for fine-grained overrides of the generated behavior. + */ + public LinkAddress[] getLinkAddresses4() { + //Suppress autogen + return null; + } + + /** + * Additionally, some methods like {@link #equals}, {@link #hashCode}, {@link #toString}, + * {@link #writeToParcel}, {@link Parcelable.Creator#createFromParcel} allow you to define + * special methods to override their behavior on a per-field basis. + * + * See the generateted methods' descriptions for the detailed instructions of what the method + * signatures for such methods are expected to be. + * + * Here we use this to "fix" {@link Pattern} not implementing equals/hashCode. + * + * @see #equals + * @see #hashCode + */ + private boolean patternEquals(Pattern other) { + return Objects.equals(mPattern.pattern(), other.pattern()); + } + private int patternHashCode() { + return Objects.hashCode(mPattern.pattern()); + } + + /** + * Similarly, {@link #onConstructed()}, if defined, gets called at the end of constructing an + * instance. + * + * At this point all fields should be in place, so this is the right place to put any custom + * validation logic. + */ + private void onConstructed() { + Preconditions.checkState(mNum2 == mNum4); + } + + /** + * {@link DataClass#genForEachField} can be used to generate a generic {@link #forEachField} + * utility, which can be used for various use-cases not covered out of the box. + * Callback passed to {@link #forEachField} will be called once per each property with its name + * and value. + * + * Here for example it's used to implement a typical dump method. + * + * Note that there are 2 {@link #forEachField} versions provided, one that treats each field + * value as an {@link Object}, thus boxing primitives if any, and one that additionally takes + * specialized callbacks for particular primitive field types used in given class. + * + * Some primitives like {@link Boolean}s and {@link Integer}s within [-128, 127] don't allocate + * when boxed, so it's up to you to decide which one to use for a given use-case. + */ + public void dump(PrintWriter pw) { + forEachField((self, name, value) -> { + pw.append(" ").append(name).append(": ").append(String.valueOf(value)).append('\n'); + }); + } + + + + // Code below generated by codegen v1.0.15. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @IntDef(prefix = "STATE_", value = { + STATE_UNDEFINED, + STATE_ON, + STATE_OFF + }) + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface State {} + + @DataClass.Generated.Member + public static String stateToString(@State int value) { + switch (value) { + case STATE_UNDEFINED: + return "STATE_UNDEFINED"; + case STATE_ON: + return "STATE_ON"; + case STATE_OFF: + return "STATE_OFF"; + default: return Integer.toHexString(value); + } + } + + @IntDef(flag = true, prefix = "FLAG_", value = { + FLAG_MANUAL_REQUEST, + FLAG_COMPATIBILITY_MODE_REQUEST, + FLAG_AUGMENTED_REQUEST + }) + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface RequestFlags {} + + @DataClass.Generated.Member + public static String requestFlagsToString(@RequestFlags int value) { + return com.android.internal.util.BitUtils.flagsToString( + value, SampleDataClass::singleRequestFlagsToString); + } + + @DataClass.Generated.Member + static String singleRequestFlagsToString(@RequestFlags int value) { + switch (value) { + case FLAG_MANUAL_REQUEST: + return "FLAG_MANUAL_REQUEST"; + case FLAG_COMPATIBILITY_MODE_REQUEST: + return "FLAG_COMPATIBILITY_MODE_REQUEST"; + case FLAG_AUGMENTED_REQUEST: + return "FLAG_AUGMENTED_REQUEST"; + default: return Integer.toHexString(value); + } + } + + @StringDef(prefix = "STATE_NAME_", value = { + STATE_NAME_UNDEFINED, + STATE_NAME_ON, + STATE_NAME_OFF + }) + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface StateName {} + + /** + * Creates a new SampleDataClass. + * + * @param num + * Any property javadoc should go onto the field, and will be copied where appropriate, + * including getters, constructor parameters, builder setters, etc. + * + * <p> + * This allows to avoid the burden of maintaining copies of the same documentation + * pieces in multiple places for each field. + * @param num2 + * Various javadoc features should work as expected when copied, e.g {@code code}, + * {@link #mName links}, <a href="https://google.com">html links</a>, etc. + * @param num4 + * {@code @hide} javadoc annotation is also propagated, which can be used to adjust the + * desired public API surface. + * @param name + * {@link Nullable} or {@link NonNull} annotation is required on all non-primitive fields. + * @param name2 + * Fields with default value expressions ("mFoo = ...") are optional, and are automatically + * initialized to the provided default expression, unless explicitly set. + * + * When using a {@link Builder} optional fields are passed via a {@link Builder#setName2 setter} + * while mandatory fields are passed via {@link Builder#Builder constructor}. + * @param name4 + * Alternatively, when default value computation is expensive, + * {@link #defaultName4 defaultFieldName()} can be defined to compute the default value. + * @param otherParcelable + * For parcelling, any field type supported by {@link Parcel} is supported out of the box. + * E.g. {@link Parcelable} subclasses, {@link String}, {@link int}, {@link boolean}, etc. + * @param date + * Additionally, support for parcelling other types can be added by implementing a + * {@link Parcelling}, and referencing it in the {@link DataClass.ParcelWith} field annotation. + * @param pattern + * If a {@link Parcelling} is fairly common, consider putting it in {@link Parcelling.BuiltIn} + * to encourage its reuse. + * @param linkAddresses2 + * For lists, when using a {@link Builder}, other than a regular + * {@link Builder#setLinkAddresses2(List) setter}, and additional + * {@link Builder#addLinkAddresses2(LinkAddress) add} method is generated for convenience. + * @param linkAddresses + * For aesthetics, you may want to consider providing a singular version of the plural field + * name, which would be used for the {@link #mLinkAddresses2 above mentioned} "add" method. + * @param linkAddresses4 + * For array fields, when using a {@link Builder}, vararg argument format is used for + * convenience. + * @param stateName + * {@link IntDef}/{@link StringDef}-annotated fields propagate the annotation to + * getter/constructor/setter/builder parameters, making for a nicer api. + * @param flags + * Fields annotated with {@link IntDef} annotations also get a proper {@link #toString()} value. + * @param state + * Above is true for both {@link IntDef#flag flags} and enum-like {@link IntDef}s + * @param charSeq + * Making a field public will suppress getter generation in favor of accessing it directly. + * @param linkAddresses5 + * Final fields suppress generating a setter (when setters are requested). + * @param stringRes + * Fields with certain annotations are automatically validated in constructor + * + * You can see overloads in {@link AnnotationValidations} for a list of currently + * supported ones. + * + * You can also extend support to your custom annotations by creating another corresponding + * overloads like + * {@link AnnotationValidations#validate(Class, UserIdInt, int)}. + * @param dayOfWeek + * Validation annotations may also have parameters. + * + * Parameter values will be supplied to validation method as name-value pairs. + * @param coords + * Unnamed validation annotation parameter gets supplied to the validating method named as + * "value". + * + * Validation annotations following {@link Each} annotation, will be applied for each + * array/collection element instead. + */ + @DataClass.Generated.Member + public SampleDataClass( + int num, + int num2, + int num4, + @Nullable String name, + @NonNull String name2, + @NonNull String name4, + @Nullable AccessibilityNodeInfo otherParcelable, + @NonNull Date date, + @NonNull Pattern pattern, + @NonNull List<LinkAddress> linkAddresses2, + @NonNull ArrayList<LinkAddress> linkAddresses, + @Nullable LinkAddress[] linkAddresses4, + @StateName @NonNull String stateName, + @RequestFlags int flags, + @State int state, + @NonNull CharSequence charSeq, + @Nullable LinkAddress[] linkAddresses5, + @StringRes int stringRes, + @android.annotation.IntRange(from = 0, to = 6) int dayOfWeek, + @Size(2) @NonNull @FloatRange(from = 0f) float[] coords) { + this.mNum = num; + this.mNum2 = num2; + this.mNum4 = num4; + this.mName = name; + this.mName2 = name2; + AnnotationValidations.validate( + NonNull.class, null, mName2); + this.mName4 = name4; + AnnotationValidations.validate( + NonNull.class, null, mName4); + this.mOtherParcelable = otherParcelable; + this.mDate = date; + AnnotationValidations.validate( + NonNull.class, null, mDate); + this.mPattern = pattern; + AnnotationValidations.validate( + NonNull.class, null, mPattern); + this.mLinkAddresses2 = linkAddresses2; + AnnotationValidations.validate( + NonNull.class, null, mLinkAddresses2); + this.mLinkAddresses = linkAddresses; + AnnotationValidations.validate( + NonNull.class, null, mLinkAddresses); + this.mLinkAddresses4 = linkAddresses4; + this.mStateName = stateName; + + if (!(Objects.equals(mStateName, STATE_NAME_UNDEFINED)) + && !(Objects.equals(mStateName, STATE_NAME_ON)) + && !(Objects.equals(mStateName, STATE_NAME_OFF))) { + throw new java.lang.IllegalArgumentException( + "stateName was " + mStateName + " but must be one of: " + + "STATE_NAME_UNDEFINED(" + STATE_NAME_UNDEFINED + "), " + + "STATE_NAME_ON(" + STATE_NAME_ON + "), " + + "STATE_NAME_OFF(" + STATE_NAME_OFF + ")"); + } + + AnnotationValidations.validate( + NonNull.class, null, mStateName); + this.mFlags = flags; + + Preconditions.checkFlagsArgument( + mFlags, + FLAG_MANUAL_REQUEST + | FLAG_COMPATIBILITY_MODE_REQUEST + | FLAG_AUGMENTED_REQUEST); + this.mState = state; + + if (!(mState == STATE_UNDEFINED) + && !(mState == STATE_ON) + && !(mState == STATE_OFF)) { + throw new java.lang.IllegalArgumentException( + "state was " + mState + " but must be one of: " + + "STATE_UNDEFINED(" + STATE_UNDEFINED + "), " + + "STATE_ON(" + STATE_ON + "), " + + "STATE_OFF(" + STATE_OFF + ")"); + } + + this.charSeq = charSeq; + AnnotationValidations.validate( + NonNull.class, null, charSeq); + this.mLinkAddresses5 = linkAddresses5; + this.mStringRes = stringRes; + AnnotationValidations.validate( + StringRes.class, null, mStringRes); + this.mDayOfWeek = dayOfWeek; + AnnotationValidations.validate( + android.annotation.IntRange.class, null, mDayOfWeek, + "from", 0, + "to", 6); + this.mCoords = coords; + AnnotationValidations.validate( + Size.class, null, mCoords.length, + "value", 2); + AnnotationValidations.validate( + NonNull.class, null, mCoords); + int coordsSize = mCoords.length; + for (int i = 0; i < coordsSize; i++) { + AnnotationValidations.validate( + FloatRange.class, null, mCoords[i], + "from", 0f); + } + + + onConstructed(); + } + + /** + * Any property javadoc should go onto the field, and will be copied where appropriate, + * including getters, constructor parameters, builder setters, etc. + * + * <p> + * This allows to avoid the burden of maintaining copies of the same documentation + * pieces in multiple places for each field. + */ + @DataClass.Generated.Member + public int getNum() { + return mNum; + } + + /** + * Various javadoc features should work as expected when copied, e.g {@code code}, + * {@link #mName links}, <a href="https://google.com">html links</a>, etc. + * + * @see #mNum2 ..and so should blocks at the bottom, e.g. {@code @see} blocks. + */ + @DataClass.Generated.Member + public int getNum2() { + return mNum2; + } + + /** + * {@code @hide} javadoc annotation is also propagated, which can be used to adjust the + * desired public API surface. + * + * @see #getNum4() is hidden + * @see Builder#setNum4(int) also hidden + * @hide + */ + @DataClass.Generated.Member + public int getNum4() { + return mNum4; + } + + /** + * {@link Nullable} or {@link NonNull} annotation is required on all non-primitive fields. + */ + @DataClass.Generated.Member + public @Nullable String getName() { + return mName; + } + + /** + * Fields with default value expressions ("mFoo = ...") are optional, and are automatically + * initialized to the provided default expression, unless explicitly set. + * + * When using a {@link Builder} optional fields are passed via a {@link Builder#setName2 setter} + * while mandatory fields are passed via {@link Builder#Builder constructor}. + */ + @DataClass.Generated.Member + public @NonNull String getName2() { + return mName2; + } + + /** + * Alternatively, when default value computation is expensive, + * {@link #defaultName4 defaultFieldName()} can be defined to compute the default value. + */ + @DataClass.Generated.Member + public @NonNull String getName4() { + return mName4; + } + + /** + * For parcelling, any field type supported by {@link Parcel} is supported out of the box. + * E.g. {@link Parcelable} subclasses, {@link String}, {@link int}, {@link boolean}, etc. + */ + @DataClass.Generated.Member + public @Nullable AccessibilityNodeInfo getOtherParcelable() { + return mOtherParcelable; + } + + /** + * Additionally, support for parcelling other types can be added by implementing a + * {@link Parcelling}, and referencing it in the {@link DataClass.ParcelWith} field annotation. + * + * @see MyDateParcelling an example {@link Parcelling} implementation + */ + @DataClass.Generated.Member + public @NonNull Date getDate() { + return mDate; + } + + /** + * If a {@link Parcelling} is fairly common, consider putting it in {@link Parcelling.BuiltIn} + * to encourage its reuse. + */ + @DataClass.Generated.Member + public @NonNull Pattern getPattern() { + return mPattern; + } + + /** + * For lists, when using a {@link Builder}, other than a regular + * {@link Builder#setLinkAddresses2(List) setter}, and additional + * {@link Builder#addLinkAddresses2(LinkAddress) add} method is generated for convenience. + */ + @DataClass.Generated.Member + public @NonNull List<LinkAddress> getLinkAddresses2() { + return mLinkAddresses2; + } + + /** + * For aesthetics, you may want to consider providing a singular version of the plural field + * name, which would be used for the {@link #mLinkAddresses2 above mentioned} "add" method. + * + * @see Builder#addLinkAddress(LinkAddress) + */ + @DataClass.Generated.Member + public @NonNull ArrayList<LinkAddress> getLinkAddresses() { + return mLinkAddresses; + } + + /** + * {@link IntDef}/{@link StringDef}-annotated fields propagate the annotation to + * getter/constructor/setter/builder parameters, making for a nicer api. + * + * @see #getStateName + * @see Builder#setStateName + */ + @DataClass.Generated.Member + public @StateName @NonNull String getStateName() { + return mStateName; + } + + /** + * Fields annotated with {@link IntDef} annotations also get a proper {@link #toString()} value. + */ + @DataClass.Generated.Member + public @RequestFlags int getFlags() { + return mFlags; + } + + /** + * Above is true for both {@link IntDef#flag flags} and enum-like {@link IntDef}s + */ + @DataClass.Generated.Member + public @State int getState() { + return mState; + } + + /** + * Final fields suppress generating a setter (when setters are requested). + */ + @DataClass.Generated.Member + public @Nullable LinkAddress[] getLinkAddresses5() { + return mLinkAddresses5; + } + + /** + * Fields with certain annotations are automatically validated in constructor + * + * You can see overloads in {@link AnnotationValidations} for a list of currently + * supported ones. + * + * You can also extend support to your custom annotations by creating another corresponding + * overloads like + * {@link AnnotationValidations#validate(Class, UserIdInt, int)}. + * + * @see #SampleDataClass + */ + @DataClass.Generated.Member + public @StringRes int getStringRes() { + return mStringRes; + } + + /** + * Validation annotations may also have parameters. + * + * Parameter values will be supplied to validation method as name-value pairs. + * + * @see AnnotationValidations#validate(Class, Size, int, String, int, String, int) + */ + @DataClass.Generated.Member + public @android.annotation.IntRange(from = 0, to = 6) int getDayOfWeek() { + return mDayOfWeek; + } + + /** + * Unnamed validation annotation parameter gets supplied to the validating method named as + * "value". + * + * Validation annotations following {@link Each} annotation, will be applied for each + * array/collection element instead. + * + * @see AnnotationValidations#validate(Class, Size, int, String, int) + */ + @DataClass.Generated.Member + public @Size(2) @NonNull @FloatRange(from = 0f) float[] getCoords() { + return mCoords; + } + + /** + * When using transient fields for caching it's often also a good idea to initialize them + * lazily. + * + * You can declare a special method like {@link #lazyInitTmpStorage()}, to let the + * {@link #getTmpStorage getter} lazily-initialize the value on demand. + */ + @DataClass.Generated.Member + public int[] getTmpStorage() { + int[] tmpStorage = mTmpStorage; + if (tmpStorage == null) { + // You can mark field as volatile for thread-safe double-check init + tmpStorage = mTmpStorage = lazyInitTmpStorage(); + } + return tmpStorage; + } + + /** + * Any property javadoc should go onto the field, and will be copied where appropriate, + * including getters, constructor parameters, builder setters, etc. + * + * <p> + * This allows to avoid the burden of maintaining copies of the same documentation + * pieces in multiple places for each field. + */ + @DataClass.Generated.Member + public SampleDataClass setNum( int value) { + mNum = value; + return this; + } + + /** + * Various javadoc features should work as expected when copied, e.g {@code code}, + * {@link #mName links}, <a href="https://google.com">html links</a>, etc. + * + * @see #mNum2 ..and so should blocks at the bottom, e.g. {@code @see} blocks. + */ + @DataClass.Generated.Member + public SampleDataClass setNum2( int value) { + mNum2 = value; + return this; + } + + /** + * {@code @hide} javadoc annotation is also propagated, which can be used to adjust the + * desired public API surface. + * + * @see #getNum4() is hidden + * @see Builder#setNum4(int) also hidden + * @hide + */ + @DataClass.Generated.Member + public SampleDataClass setNum4( int value) { + mNum4 = value; + return this; + } + + /** + * {@link Nullable} or {@link NonNull} annotation is required on all non-primitive fields. + */ + @DataClass.Generated.Member + public SampleDataClass setName(@NonNull String value) { + mName = value; + return this; + } + + /** + * Fields with default value expressions ("mFoo = ...") are optional, and are automatically + * initialized to the provided default expression, unless explicitly set. + * + * When using a {@link Builder} optional fields are passed via a {@link Builder#setName2 setter} + * while mandatory fields are passed via {@link Builder#Builder constructor}. + */ + @DataClass.Generated.Member + public SampleDataClass setName2(@NonNull String value) { + mName2 = value; + AnnotationValidations.validate( + NonNull.class, null, mName2); + return this; + } + + /** + * Alternatively, when default value computation is expensive, + * {@link #defaultName4 defaultFieldName()} can be defined to compute the default value. + */ + @DataClass.Generated.Member + public SampleDataClass setName4(@NonNull String value) { + mName4 = value; + AnnotationValidations.validate( + NonNull.class, null, mName4); + return this; + } + + /** + * For parcelling, any field type supported by {@link Parcel} is supported out of the box. + * E.g. {@link Parcelable} subclasses, {@link String}, {@link int}, {@link boolean}, etc. + */ + @DataClass.Generated.Member + public SampleDataClass setOtherParcelable(@NonNull AccessibilityNodeInfo value) { + mOtherParcelable = value; + return this; + } + + /** + * Additionally, support for parcelling other types can be added by implementing a + * {@link Parcelling}, and referencing it in the {@link DataClass.ParcelWith} field annotation. + * + * @see MyDateParcelling an example {@link Parcelling} implementation + */ + @DataClass.Generated.Member + public SampleDataClass setDate(@NonNull Date value) { + mDate = value; + AnnotationValidations.validate( + NonNull.class, null, mDate); + return this; + } + + /** + * If a {@link Parcelling} is fairly common, consider putting it in {@link Parcelling.BuiltIn} + * to encourage its reuse. + */ + @DataClass.Generated.Member + public SampleDataClass setPattern(@NonNull Pattern value) { + mPattern = value; + AnnotationValidations.validate( + NonNull.class, null, mPattern); + return this; + } + + /** + * For lists, when using a {@link Builder}, other than a regular + * {@link Builder#setLinkAddresses2(List) setter}, and additional + * {@link Builder#addLinkAddresses2(LinkAddress) add} method is generated for convenience. + */ + @DataClass.Generated.Member + public SampleDataClass setLinkAddresses2(@NonNull List<LinkAddress> value) { + mLinkAddresses2 = value; + AnnotationValidations.validate( + NonNull.class, null, mLinkAddresses2); + return this; + } + + /** + * For aesthetics, you may want to consider providing a singular version of the plural field + * name, which would be used for the {@link #mLinkAddresses2 above mentioned} "add" method. + * + * @see Builder#addLinkAddress(LinkAddress) + */ + @DataClass.Generated.Member + public SampleDataClass setLinkAddresses(@NonNull ArrayList<LinkAddress> value) { + mLinkAddresses = value; + AnnotationValidations.validate( + NonNull.class, null, mLinkAddresses); + return this; + } + + /** + * For array fields, when using a {@link Builder}, vararg argument format is used for + * convenience. + * + * @see Builder#setLinkAddresses4(LinkAddress...) + */ + @DataClass.Generated.Member + public SampleDataClass setLinkAddresses4(@NonNull LinkAddress... value) { + mLinkAddresses4 = value; + return this; + } + + /** + * {@link IntDef}/{@link StringDef}-annotated fields propagate the annotation to + * getter/constructor/setter/builder parameters, making for a nicer api. + * + * @see #getStateName + * @see Builder#setStateName + */ + @DataClass.Generated.Member + public SampleDataClass setStateName(@StateName @NonNull String value) { + mStateName = value; + + if (!(Objects.equals(mStateName, STATE_NAME_UNDEFINED)) + && !(Objects.equals(mStateName, STATE_NAME_ON)) + && !(Objects.equals(mStateName, STATE_NAME_OFF))) { + throw new java.lang.IllegalArgumentException( + "stateName was " + mStateName + " but must be one of: " + + "STATE_NAME_UNDEFINED(" + STATE_NAME_UNDEFINED + "), " + + "STATE_NAME_ON(" + STATE_NAME_ON + "), " + + "STATE_NAME_OFF(" + STATE_NAME_OFF + ")"); + } + + AnnotationValidations.validate( + NonNull.class, null, mStateName); + return this; + } + + /** + * Fields annotated with {@link IntDef} annotations also get a proper {@link #toString()} value. + */ + @DataClass.Generated.Member + public SampleDataClass setFlags(@RequestFlags int value) { + mFlags = value; + + Preconditions.checkFlagsArgument( + mFlags, + FLAG_MANUAL_REQUEST + | FLAG_COMPATIBILITY_MODE_REQUEST + | FLAG_AUGMENTED_REQUEST); + return this; + } + + /** + * Above is true for both {@link IntDef#flag flags} and enum-like {@link IntDef}s + */ + @DataClass.Generated.Member + public SampleDataClass setState(@State int value) { + mState = value; + + if (!(mState == STATE_UNDEFINED) + && !(mState == STATE_ON) + && !(mState == STATE_OFF)) { + throw new java.lang.IllegalArgumentException( + "state was " + mState + " but must be one of: " + + "STATE_UNDEFINED(" + STATE_UNDEFINED + "), " + + "STATE_ON(" + STATE_ON + "), " + + "STATE_OFF(" + STATE_OFF + ")"); + } + + return this; + } + + /** + * Fields with certain annotations are automatically validated in constructor + * + * You can see overloads in {@link AnnotationValidations} for a list of currently + * supported ones. + * + * You can also extend support to your custom annotations by creating another corresponding + * overloads like + * {@link AnnotationValidations#validate(Class, UserIdInt, int)}. + * + * @see #SampleDataClass + */ + @DataClass.Generated.Member + public SampleDataClass setStringRes(@StringRes int value) { + mStringRes = value; + AnnotationValidations.validate( + StringRes.class, null, mStringRes); + return this; + } + + /** + * Validation annotations may also have parameters. + * + * Parameter values will be supplied to validation method as name-value pairs. + * + * @see AnnotationValidations#validate(Class, Size, int, String, int, String, int) + */ + @DataClass.Generated.Member + public SampleDataClass setDayOfWeek(@android.annotation.IntRange(from = 0, to = 6) int value) { + mDayOfWeek = value; + AnnotationValidations.validate( + android.annotation.IntRange.class, null, mDayOfWeek, + "from", 0, + "to", 6); + return this; + } + + /** + * Unnamed validation annotation parameter gets supplied to the validating method named as + * "value". + * + * Validation annotations following {@link Each} annotation, will be applied for each + * array/collection element instead. + * + * @see AnnotationValidations#validate(Class, Size, int, String, int) + */ + @DataClass.Generated.Member + public SampleDataClass setCoords(@Size(2) @NonNull @FloatRange(from = 0f) float... value) { + mCoords = value; + AnnotationValidations.validate( + Size.class, null, mCoords.length, + "value", 2); + AnnotationValidations.validate( + NonNull.class, null, mCoords); + int coordsSize = mCoords.length; + for (int i = 0; i < coordsSize; i++) { + AnnotationValidations.validate( + FloatRange.class, null, mCoords[i], + "from", 0f); + } + + return this; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "SampleDataClass { " + + "num = " + mNum + ", " + + "num2 = " + mNum2 + ", " + + "num4 = " + mNum4 + ", " + + "name = " + mName + ", " + + "name2 = " + mName2 + ", " + + "name4 = " + mName4 + ", " + + "otherParcelable = " + mOtherParcelable + ", " + + "date = " + mDate + ", " + + "pattern = " + mPattern + ", " + + "linkAddresses2 = " + mLinkAddresses2 + ", " + + "linkAddresses = " + mLinkAddresses + ", " + + "linkAddresses4 = " + java.util.Arrays.toString(mLinkAddresses4) + ", " + + "stateName = " + mStateName + ", " + + "flags = " + requestFlagsToString(mFlags) + ", " + + "state = " + stateToString(mState) + ", " + + "charSeq = " + charSeq + ", " + + "linkAddresses5 = " + java.util.Arrays.toString(mLinkAddresses5) + ", " + + "stringRes = " + mStringRes + ", " + + "dayOfWeek = " + mDayOfWeek + ", " + + "coords = " + java.util.Arrays.toString(mCoords) + + " }"; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(SampleDataClass other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + SampleDataClass that = (SampleDataClass) o; + //noinspection PointlessBooleanExpression + return true + && mNum == that.mNum + && mNum2 == that.mNum2 + && mNum4 == that.mNum4 + && Objects.equals(mName, that.mName) + && Objects.equals(mName2, that.mName2) + && Objects.equals(mName4, that.mName4) + && Objects.equals(mOtherParcelable, that.mOtherParcelable) + && Objects.equals(mDate, that.mDate) + && patternEquals(that.mPattern) + && Objects.equals(mLinkAddresses2, that.mLinkAddresses2) + && Objects.equals(mLinkAddresses, that.mLinkAddresses) + && java.util.Arrays.equals(mLinkAddresses4, that.mLinkAddresses4) + && Objects.equals(mStateName, that.mStateName) + && mFlags == that.mFlags + && mState == that.mState + && Objects.equals(charSeq, that.charSeq) + && java.util.Arrays.equals(mLinkAddresses5, that.mLinkAddresses5) + && mStringRes == that.mStringRes + && mDayOfWeek == that.mDayOfWeek + && java.util.Arrays.equals(mCoords, that.mCoords); + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + mNum; + _hash = 31 * _hash + mNum2; + _hash = 31 * _hash + mNum4; + _hash = 31 * _hash + Objects.hashCode(mName); + _hash = 31 * _hash + Objects.hashCode(mName2); + _hash = 31 * _hash + Objects.hashCode(mName4); + _hash = 31 * _hash + Objects.hashCode(mOtherParcelable); + _hash = 31 * _hash + Objects.hashCode(mDate); + _hash = 31 * _hash + patternHashCode(); + _hash = 31 * _hash + Objects.hashCode(mLinkAddresses2); + _hash = 31 * _hash + Objects.hashCode(mLinkAddresses); + _hash = 31 * _hash + java.util.Arrays.hashCode(mLinkAddresses4); + _hash = 31 * _hash + Objects.hashCode(mStateName); + _hash = 31 * _hash + mFlags; + _hash = 31 * _hash + mState; + _hash = 31 * _hash + Objects.hashCode(charSeq); + _hash = 31 * _hash + java.util.Arrays.hashCode(mLinkAddresses5); + _hash = 31 * _hash + mStringRes; + _hash = 31 * _hash + mDayOfWeek; + _hash = 31 * _hash + java.util.Arrays.hashCode(mCoords); + return _hash; + } + + @DataClass.Generated.Member + void forEachField( + @NonNull DataClass.PerIntFieldAction<SampleDataClass> actionInt, + @NonNull DataClass.PerObjectFieldAction<SampleDataClass> actionObject) { + actionInt.acceptInt(this, "num", mNum); + actionInt.acceptInt(this, "num2", mNum2); + actionInt.acceptInt(this, "num4", mNum4); + actionObject.acceptObject(this, "name", mName); + actionObject.acceptObject(this, "name2", mName2); + actionObject.acceptObject(this, "name4", mName4); + actionObject.acceptObject(this, "otherParcelable", mOtherParcelable); + actionObject.acceptObject(this, "date", mDate); + actionObject.acceptObject(this, "pattern", mPattern); + actionObject.acceptObject(this, "linkAddresses2", mLinkAddresses2); + actionObject.acceptObject(this, "linkAddresses", mLinkAddresses); + actionObject.acceptObject(this, "linkAddresses4", mLinkAddresses4); + actionObject.acceptObject(this, "stateName", mStateName); + actionInt.acceptInt(this, "flags", mFlags); + actionInt.acceptInt(this, "state", mState); + actionObject.acceptObject(this, "charSeq", charSeq); + actionObject.acceptObject(this, "linkAddresses5", mLinkAddresses5); + actionInt.acceptInt(this, "stringRes", mStringRes); + actionInt.acceptInt(this, "dayOfWeek", mDayOfWeek); + actionObject.acceptObject(this, "coords", mCoords); + } + + /** @deprecated May cause boxing allocations - use with caution! */ + @Deprecated + @DataClass.Generated.Member + void forEachField(@NonNull DataClass.PerObjectFieldAction<SampleDataClass> action) { + action.acceptObject(this, "num", mNum); + action.acceptObject(this, "num2", mNum2); + action.acceptObject(this, "num4", mNum4); + action.acceptObject(this, "name", mName); + action.acceptObject(this, "name2", mName2); + action.acceptObject(this, "name4", mName4); + action.acceptObject(this, "otherParcelable", mOtherParcelable); + action.acceptObject(this, "date", mDate); + action.acceptObject(this, "pattern", mPattern); + action.acceptObject(this, "linkAddresses2", mLinkAddresses2); + action.acceptObject(this, "linkAddresses", mLinkAddresses); + action.acceptObject(this, "linkAddresses4", mLinkAddresses4); + action.acceptObject(this, "stateName", mStateName); + action.acceptObject(this, "flags", mFlags); + action.acceptObject(this, "state", mState); + action.acceptObject(this, "charSeq", charSeq); + action.acceptObject(this, "linkAddresses5", mLinkAddresses5); + action.acceptObject(this, "stringRes", mStringRes); + action.acceptObject(this, "dayOfWeek", mDayOfWeek); + action.acceptObject(this, "coords", mCoords); + } + + @DataClass.Generated.Member + static Parcelling<Date> sParcellingForDate = + Parcelling.Cache.get( + MyDateParcelling.class); + static { + if (sParcellingForDate == null) { + sParcellingForDate = Parcelling.Cache.put( + new MyDateParcelling()); + } + } + + @DataClass.Generated.Member + static Parcelling<Pattern> sParcellingForPattern = + Parcelling.Cache.get( + Parcelling.BuiltIn.ForPattern.class); + static { + if (sParcellingForPattern == null) { + sParcellingForPattern = Parcelling.Cache.put( + new Parcelling.BuiltIn.ForPattern()); + } + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + long flg = 0; + if (mName != null) flg |= 0x8; + if (mOtherParcelable != null) flg |= 0x40; + if (mLinkAddresses4 != null) flg |= 0x800; + if (mLinkAddresses5 != null) flg |= 0x10000; + dest.writeLong(flg); + dest.writeInt(mNum); + dest.writeInt(mNum2); + dest.writeInt(mNum4); + if (mName != null) dest.writeString(mName); + dest.writeString(mName2); + dest.writeString(mName4); + if (mOtherParcelable != null) dest.writeTypedObject(mOtherParcelable, flags); + sParcellingForDate.parcel(mDate, dest, flags); + sParcellingForPattern.parcel(mPattern, dest, flags); + dest.writeParcelableList(mLinkAddresses2, flags); + dest.writeParcelableList(mLinkAddresses, flags); + if (mLinkAddresses4 != null) dest.writeTypedArray(mLinkAddresses4, flags); + dest.writeString(mStateName); + dest.writeInt(mFlags); + dest.writeInt(mState); + dest.writeCharSequence(charSeq); + if (mLinkAddresses5 != null) dest.writeTypedArray(mLinkAddresses5, flags); + dest.writeInt(mStringRes); + dest.writeInt(mDayOfWeek); + dest.writeFloatArray(mCoords); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ SampleDataClass(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + long flg = in.readLong(); + int num = in.readInt(); + int num2 = in.readInt(); + int num4 = in.readInt(); + String name = (flg & 0x8) == 0 ? null : in.readString(); + String name2 = in.readString(); + String name4 = in.readString(); + AccessibilityNodeInfo otherParcelable = (flg & 0x40) == 0 ? null : (AccessibilityNodeInfo) in.readTypedObject(AccessibilityNodeInfo.CREATOR); + Date date = sParcellingForDate.unparcel(in); + Pattern pattern = sParcellingForPattern.unparcel(in); + List<LinkAddress> linkAddresses2 = new ArrayList<>(); + in.readParcelableList(linkAddresses2, LinkAddress.class.getClassLoader()); + ArrayList<LinkAddress> linkAddresses = new ArrayList<>(); + in.readParcelableList(linkAddresses, LinkAddress.class.getClassLoader()); + LinkAddress[] linkAddresses4 = (flg & 0x800) == 0 ? null : (LinkAddress[]) in.createTypedArray(LinkAddress.CREATOR); + String stateName = in.readString(); + int flags = in.readInt(); + int state = in.readInt(); + CharSequence _charSeq = (CharSequence) in.readCharSequence(); + LinkAddress[] linkAddresses5 = (flg & 0x10000) == 0 ? null : (LinkAddress[]) in.createTypedArray(LinkAddress.CREATOR); + int stringRes = in.readInt(); + int dayOfWeek = in.readInt(); + float[] coords = in.createFloatArray(); + + this.mNum = num; + this.mNum2 = num2; + this.mNum4 = num4; + this.mName = name; + this.mName2 = name2; + AnnotationValidations.validate( + NonNull.class, null, mName2); + this.mName4 = name4; + AnnotationValidations.validate( + NonNull.class, null, mName4); + this.mOtherParcelable = otherParcelable; + this.mDate = date; + AnnotationValidations.validate( + NonNull.class, null, mDate); + this.mPattern = pattern; + AnnotationValidations.validate( + NonNull.class, null, mPattern); + this.mLinkAddresses2 = linkAddresses2; + AnnotationValidations.validate( + NonNull.class, null, mLinkAddresses2); + this.mLinkAddresses = linkAddresses; + AnnotationValidations.validate( + NonNull.class, null, mLinkAddresses); + this.mLinkAddresses4 = linkAddresses4; + this.mStateName = stateName; + + if (!(Objects.equals(mStateName, STATE_NAME_UNDEFINED)) + && !(Objects.equals(mStateName, STATE_NAME_ON)) + && !(Objects.equals(mStateName, STATE_NAME_OFF))) { + throw new java.lang.IllegalArgumentException( + "stateName was " + mStateName + " but must be one of: " + + "STATE_NAME_UNDEFINED(" + STATE_NAME_UNDEFINED + "), " + + "STATE_NAME_ON(" + STATE_NAME_ON + "), " + + "STATE_NAME_OFF(" + STATE_NAME_OFF + ")"); + } + + AnnotationValidations.validate( + NonNull.class, null, mStateName); + this.mFlags = flags; + + Preconditions.checkFlagsArgument( + mFlags, + FLAG_MANUAL_REQUEST + | FLAG_COMPATIBILITY_MODE_REQUEST + | FLAG_AUGMENTED_REQUEST); + this.mState = state; + + if (!(mState == STATE_UNDEFINED) + && !(mState == STATE_ON) + && !(mState == STATE_OFF)) { + throw new java.lang.IllegalArgumentException( + "state was " + mState + " but must be one of: " + + "STATE_UNDEFINED(" + STATE_UNDEFINED + "), " + + "STATE_ON(" + STATE_ON + "), " + + "STATE_OFF(" + STATE_OFF + ")"); + } + + this.charSeq = _charSeq; + AnnotationValidations.validate( + NonNull.class, null, charSeq); + this.mLinkAddresses5 = linkAddresses5; + this.mStringRes = stringRes; + AnnotationValidations.validate( + StringRes.class, null, mStringRes); + this.mDayOfWeek = dayOfWeek; + AnnotationValidations.validate( + android.annotation.IntRange.class, null, mDayOfWeek, + "from", 0, + "to", 6); + this.mCoords = coords; + AnnotationValidations.validate( + Size.class, null, mCoords.length, + "value", 2); + AnnotationValidations.validate( + NonNull.class, null, mCoords); + int coordsSize = mCoords.length; + for (int i = 0; i < coordsSize; i++) { + AnnotationValidations.validate( + FloatRange.class, null, mCoords[i], + "from", 0f); + } + + + onConstructed(); + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<SampleDataClass> CREATOR + = new Parcelable.Creator<SampleDataClass>() { + @Override + public SampleDataClass[] newArray(int size) { + return new SampleDataClass[size]; + } + + @Override + public SampleDataClass createFromParcel(@NonNull Parcel in) { + return new SampleDataClass(in); + } + }; + + /** + * A builder for {@link SampleDataClass} + */ + @SuppressWarnings("WeakerAccess") + @DataClass.Generated.Member + public static final class Builder { + + private int mNum; + private int mNum2; + private int mNum4; + private @Nullable String mName; + private @NonNull String mName2; + private @NonNull String mName4; + private @Nullable AccessibilityNodeInfo mOtherParcelable; + private @NonNull Date mDate; + private @NonNull Pattern mPattern; + private @NonNull List<LinkAddress> mLinkAddresses2; + private @NonNull ArrayList<LinkAddress> mLinkAddresses; + private @Nullable LinkAddress[] mLinkAddresses4; + private @StateName @NonNull String mStateName; + private @RequestFlags int mFlags; + private @State int mState; + private @NonNull CharSequence charSeq; + private @Nullable LinkAddress[] mLinkAddresses5; + private @StringRes int mStringRes; + private @android.annotation.IntRange(from = 0, to = 6) int mDayOfWeek; + private @Size(2) @NonNull @FloatRange(from = 0f) float[] mCoords; + + private long mBuilderFieldsSet = 0L; + + /** + * Creates a new Builder. + * + * @param num + * Any property javadoc should go onto the field, and will be copied where appropriate, + * including getters, constructor parameters, builder setters, etc. + * + * <p> + * This allows to avoid the burden of maintaining copies of the same documentation + * pieces in multiple places for each field. + * @param num2 + * Various javadoc features should work as expected when copied, e.g {@code code}, + * {@link #mName links}, <a href="https://google.com">html links</a>, etc. + * @param num4 + * {@code @hide} javadoc annotation is also propagated, which can be used to adjust the + * desired public API surface. + * @param name + * {@link Nullable} or {@link NonNull} annotation is required on all non-primitive fields. + * @param flags + * Fields annotated with {@link IntDef} annotations also get a proper {@link #toString()} value. + * @param linkAddresses5 + * Final fields suppress generating a setter (when setters are requested). + */ + public Builder( + int num, + int num2, + int num4, + @Nullable String name, + @RequestFlags int flags, + @Nullable LinkAddress[] linkAddresses5) { + mNum = num; + mNum2 = num2; + mNum4 = num4; + mName = name; + mFlags = flags; + + Preconditions.checkFlagsArgument( + mFlags, + FLAG_MANUAL_REQUEST + | FLAG_COMPATIBILITY_MODE_REQUEST + | FLAG_AUGMENTED_REQUEST); + mLinkAddresses5 = linkAddresses5; + } + + /** + * Any property javadoc should go onto the field, and will be copied where appropriate, + * including getters, constructor parameters, builder setters, etc. + * + * <p> + * This allows to avoid the burden of maintaining copies of the same documentation + * pieces in multiple places for each field. + */ + @DataClass.Generated.Member + public @NonNull Builder setNum(int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mNum = value; + return this; + } + + /** + * Various javadoc features should work as expected when copied, e.g {@code code}, + * {@link #mName links}, <a href="https://google.com">html links</a>, etc. + * + * @see #mNum2 ..and so should blocks at the bottom, e.g. {@code @see} blocks. + */ + @DataClass.Generated.Member + public @NonNull Builder setNum2(int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mNum2 = value; + return this; + } + + /** + * {@code @hide} javadoc annotation is also propagated, which can be used to adjust the + * desired public API surface. + * + * @see #getNum4() is hidden + * @see Builder#setNum4(int) also hidden + * @hide + */ + @DataClass.Generated.Member + public @NonNull Builder setNum4(int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mNum4 = value; + return this; + } + + /** + * {@link Nullable} or {@link NonNull} annotation is required on all non-primitive fields. + */ + @DataClass.Generated.Member + public @NonNull Builder setName(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x8; + mName = value; + return this; + } + + /** + * Fields with default value expressions ("mFoo = ...") are optional, and are automatically + * initialized to the provided default expression, unless explicitly set. + * + * When using a {@link Builder} optional fields are passed via a {@link Builder#setName2 setter} + * while mandatory fields are passed via {@link Builder#Builder constructor}. + */ + @DataClass.Generated.Member + public @NonNull Builder setName2(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x10; + mName2 = value; + return this; + } + + /** + * Alternatively, when default value computation is expensive, + * {@link #defaultName4 defaultFieldName()} can be defined to compute the default value. + */ + @DataClass.Generated.Member + public @NonNull Builder setName4(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x20; + mName4 = value; + return this; + } + + /** + * For parcelling, any field type supported by {@link Parcel} is supported out of the box. + * E.g. {@link Parcelable} subclasses, {@link String}, {@link int}, {@link boolean}, etc. + */ + @DataClass.Generated.Member + public @NonNull Builder setOtherParcelable(@NonNull AccessibilityNodeInfo value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x40; + mOtherParcelable = value; + return this; + } + + /** + * Additionally, support for parcelling other types can be added by implementing a + * {@link Parcelling}, and referencing it in the {@link DataClass.ParcelWith} field annotation. + * + * @see MyDateParcelling an example {@link Parcelling} implementation + */ + @DataClass.Generated.Member + public @NonNull Builder setDate(@NonNull Date value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x80; + mDate = value; + return this; + } + + /** + * If a {@link Parcelling} is fairly common, consider putting it in {@link Parcelling.BuiltIn} + * to encourage its reuse. + */ + @DataClass.Generated.Member + public @NonNull Builder setPattern(@NonNull Pattern value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x100; + mPattern = value; + return this; + } + + /** + * For lists, when using a {@link Builder}, other than a regular + * {@link Builder#setLinkAddresses2(List) setter}, and additional + * {@link Builder#addLinkAddresses2(LinkAddress) add} method is generated for convenience. + */ + @DataClass.Generated.Member + public @NonNull Builder setLinkAddresses2(@NonNull List<LinkAddress> value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x200; + mLinkAddresses2 = value; + return this; + } + + /** @see #setLinkAddresses2 */ + @DataClass.Generated.Member + public @NonNull Builder addLinkAddresses2(@NonNull LinkAddress value) { + // You can refine this method's name by providing item's singular name, e.g.: + // @DataClass.PluralOf("item")) mItems = ... + + if (mLinkAddresses2 == null) setLinkAddresses2(new ArrayList<>()); + mLinkAddresses2.add(value); + return this; + } + + /** + * For aesthetics, you may want to consider providing a singular version of the plural field + * name, which would be used for the {@link #mLinkAddresses2 above mentioned} "add" method. + * + * @see Builder#addLinkAddress(LinkAddress) + */ + @DataClass.Generated.Member + public @NonNull Builder setLinkAddresses(@NonNull ArrayList<LinkAddress> value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x400; + mLinkAddresses = value; + return this; + } + + /** @see #setLinkAddresses */ + @DataClass.Generated.Member + public @NonNull Builder addLinkAddress(@NonNull LinkAddress value) { + if (mLinkAddresses == null) setLinkAddresses(new ArrayList<>()); + mLinkAddresses.add(value); + return this; + } + + /** + * For array fields, when using a {@link Builder}, vararg argument format is used for + * convenience. + * + * @see Builder#setLinkAddresses4(LinkAddress...) + */ + @DataClass.Generated.Member + public @NonNull Builder setLinkAddresses4(@NonNull LinkAddress... value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x800; + mLinkAddresses4 = value; + return this; + } + + /** + * {@link IntDef}/{@link StringDef}-annotated fields propagate the annotation to + * getter/constructor/setter/builder parameters, making for a nicer api. + * + * @see #getStateName + * @see Builder#setStateName + */ + @DataClass.Generated.Member + public @NonNull Builder setStateName(@StateName @NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1000; + mStateName = value; + return this; + } + + /** + * Fields annotated with {@link IntDef} annotations also get a proper {@link #toString()} value. + */ + @DataClass.Generated.Member + public @NonNull Builder setFlags(@RequestFlags int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2000; + mFlags = value; + return this; + } + + /** + * Above is true for both {@link IntDef#flag flags} and enum-like {@link IntDef}s + */ + @DataClass.Generated.Member + public @NonNull Builder setState(@State int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4000; + mState = value; + return this; + } + + /** + * Making a field public will suppress getter generation in favor of accessing it directly. + */ + @DataClass.Generated.Member + public @NonNull Builder setCharSeq(@NonNull CharSequence value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x8000; + charSeq = value; + return this; + } + + /** + * Final fields suppress generating a setter (when setters are requested). + */ + @DataClass.Generated.Member + public @NonNull Builder setLinkAddresses5(@NonNull LinkAddress... value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x10000; + mLinkAddresses5 = value; + return this; + } + + /** + * Fields with certain annotations are automatically validated in constructor + * + * You can see overloads in {@link AnnotationValidations} for a list of currently + * supported ones. + * + * You can also extend support to your custom annotations by creating another corresponding + * overloads like + * {@link AnnotationValidations#validate(Class, UserIdInt, int)}. + * + * @see #SampleDataClass + */ + @DataClass.Generated.Member + public @NonNull Builder setStringRes(@StringRes int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x20000; + mStringRes = value; + return this; + } + + /** + * Validation annotations may also have parameters. + * + * Parameter values will be supplied to validation method as name-value pairs. + * + * @see AnnotationValidations#validate(Class, Size, int, String, int, String, int) + */ + @DataClass.Generated.Member + public @NonNull Builder setDayOfWeek(@android.annotation.IntRange(from = 0, to = 6) int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x40000; + mDayOfWeek = value; + return this; + } + + /** + * Unnamed validation annotation parameter gets supplied to the validating method named as + * "value". + * + * Validation annotations following {@link Each} annotation, will be applied for each + * array/collection element instead. + * + * @see AnnotationValidations#validate(Class, Size, int, String, int) + */ + @DataClass.Generated.Member + public @NonNull Builder setCoords(@Size(2) @NonNull @FloatRange(from = 0f) float... value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x80000; + mCoords = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull SampleDataClass build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x100000; // Mark builder used + + if ((mBuilderFieldsSet & 0x10) == 0) { + mName2 = "Bob"; + } + if ((mBuilderFieldsSet & 0x20) == 0) { + mName4 = defaultName4(); + } + if ((mBuilderFieldsSet & 0x40) == 0) { + mOtherParcelable = null; + } + if ((mBuilderFieldsSet & 0x80) == 0) { + mDate = new Date(42 * 42); + } + if ((mBuilderFieldsSet & 0x100) == 0) { + mPattern = Pattern.compile(""); + } + if ((mBuilderFieldsSet & 0x200) == 0) { + mLinkAddresses2 = new ArrayList<>(); + } + if ((mBuilderFieldsSet & 0x400) == 0) { + mLinkAddresses = new ArrayList<>(); + } + if ((mBuilderFieldsSet & 0x800) == 0) { + mLinkAddresses4 = null; + } + if ((mBuilderFieldsSet & 0x1000) == 0) { + mStateName = STATE_NAME_UNDEFINED; + } + if ((mBuilderFieldsSet & 0x4000) == 0) { + mState = STATE_UNDEFINED; + } + if ((mBuilderFieldsSet & 0x8000) == 0) { + charSeq = ""; + } + if ((mBuilderFieldsSet & 0x20000) == 0) { + mStringRes = 0; + } + if ((mBuilderFieldsSet & 0x40000) == 0) { + mDayOfWeek = 3; + } + if ((mBuilderFieldsSet & 0x80000) == 0) { + mCoords = new float[] { 0f, 0f }; + } + SampleDataClass o = new SampleDataClass( + mNum, + mNum2, + mNum4, + mName, + mName2, + mName4, + mOtherParcelable, + mDate, + mPattern, + mLinkAddresses2, + mLinkAddresses, + mLinkAddresses4, + mStateName, + mFlags, + mState, + charSeq, + mLinkAddresses5, + mStringRes, + mDayOfWeek, + mCoords); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x100000) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } + + @DataClass.Generated( + time = 1582685647656L, + codegenVersion = "1.0.15", + sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java", + inputSignatures = "public static final java.lang.String STATE_NAME_UNDEFINED\npublic static final java.lang.String STATE_NAME_ON\npublic static final java.lang.String STATE_NAME_OFF\npublic static final int STATE_UNDEFINED\npublic static final int STATE_ON\npublic static final int STATE_OFF\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate int mNum\nprivate int mNum2\nprivate int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient android.net.LinkAddress[] mLinkAddresses6\ntransient int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange(from=0L, to=6L) int mDayOfWeek\nprivate @android.annotation.Size(2L) @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange(from=0.0) float[] mCoords\nprivate static java.lang.String defaultName4()\nprivate int[] lazyInitTmpStorage()\npublic android.net.LinkAddress[] getLinkAddresses4()\nprivate boolean patternEquals(java.util.regex.Pattern)\nprivate int patternHashCode()\nprivate void onConstructed()\npublic void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClassTest.java b/tests/Codegen/src/com/android/codegentest/SampleDataClassTest.java new file mode 100644 index 000000000000..d13257743e21 --- /dev/null +++ b/tests/Codegen/src/com/android/codegentest/SampleDataClassTest.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.codegentest; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; + +import static java.util.concurrent.TimeUnit.SECONDS; + +import android.net.LinkAddress; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.SparseArray; +import android.util.SparseIntArray; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Tests {@link SampleDataClass} after it's augmented with dataclass codegen. + * + * Use {@code $ . runTest.sh} to run. + */ +@RunWith(AndroidJUnit4.class) +public class SampleDataClassTest { + + private SampleDataClass mSpecimen = newBuilder().build(); + + private static SampleDataClass.Builder newBuilder() { + return newInvalidBuilder() + .setNum(42) + .setNum2(42) + .setNum4(42) + .setName4("foobar") + .setLinkAddresses5(); + } + + private static SampleDataClass.Builder newInvalidBuilder() { + return new SampleDataClass.Builder(1, 2, 3, "a", 0, null) + .setName("some parcelable") + .setFlags(SampleDataClass.FLAG_MANUAL_REQUEST); + } + + @Test + public void testParcelling_producesEqualInstance() { + SampleDataClass copy = parcelAndUnparcel(mSpecimen, SampleDataClass.CREATOR); + assertEquals(mSpecimen, copy); + assertEquals(mSpecimen.hashCode(), copy.hashCode()); + } + + @Test + public void testParcelling_producesInstanceWithEqualFields() { + SampleDataClass copy = parcelAndUnparcel(mSpecimen, SampleDataClass.CREATOR); + copy.forEachField((self, copyFieldName, copyFieldValue) -> { + mSpecimen.forEachField((self2, specimenFieldName, specimenFieldValue) -> { + if (copyFieldName.equals(specimenFieldName) + && !copyFieldName.equals("pattern") + && (specimenFieldValue == null + || !specimenFieldValue.getClass().isArray())) { + assertEquals("Mismatched field values for " + copyFieldName, + specimenFieldValue, copyFieldValue); + } + }); + }); + } + + @Test + public void testCustomParcelling_instanceIsCached() { + parcelAndUnparcel(mSpecimen, SampleDataClass.CREATOR); + parcelAndUnparcel(mSpecimen, SampleDataClass.CREATOR); + assertEquals(1, MyDateParcelling.sInstanceCount.get()); + } + + @Test + public void testDefaultFieldValue_isPropagated() { + assertEquals(new Date(42 * 42), mSpecimen.getDate()); + } + + @Test + public void testForEachField_avoidsBoxing() { + AtomicInteger intFieldCount = new AtomicInteger(0); + mSpecimen.forEachField( + (self, name, intValue) -> intFieldCount.getAndIncrement(), + (self, name, objectValue) -> { + if (objectValue != null) { + assertThat("Boxed field " + name, + objectValue, not(instanceOf(Integer.class))); + } + }); + assertThat(intFieldCount.get(), greaterThanOrEqualTo(1)); + } + + @Test + public void testToString_containsEachField() { + String toString = mSpecimen.toString(); + + mSpecimen.forEachField((self, name, value) -> { + assertThat(toString, containsString(name)); + if (value instanceof Integer) { + // Could be flags, their special toString tested separately + } else if (value instanceof Object[]) { + assertThat(toString, containsString(Arrays.toString((Object[]) value))); + } else if (value != null && value.getClass().isArray()) { + // Primitive array, uses multiple specialized Arrays.toString overloads + } else { + assertThat(toString, containsString("" + value)); + } + }); + } + + @Test + public void testBuilder_propagatesValuesToInstance() { + assertEquals(43, newBuilder().setNum(43).build().getNum()); + } + + @Test + public void testPluralFields_canHaveCustomSingularBuilderName() { + newBuilder().addLinkAddress(new LinkAddress("127.0.0.1/24")); + } + + @Test(expected = IllegalStateException.class) + public void testBuilder_usableOnlyOnce() { + SampleDataClass.Builder builder = newBuilder(); + builder.build(); + builder.build(); + } + + @Test(expected = IllegalStateException.class) + public void testBuilder_performsValidation() { + newInvalidBuilder().build(); + } + + @Test + public void testIntDefs_haveCorrectToString() { + int flagsAsInt = SampleDataClass.FLAG_MANUAL_REQUEST + | SampleDataClass.FLAG_COMPATIBILITY_MODE_REQUEST; + String flagsAsString = SampleDataClass.requestFlagsToString(flagsAsInt); + + assertThat(flagsAsString, containsString("MANUAL_REQUEST")); + assertThat(flagsAsString, containsString("COMPATIBILITY_MODE_REQUEST")); + assertThat(flagsAsString, not(containsString("1"))); + assertThat(flagsAsString, not(containsString("" + flagsAsInt))); + + String dataclassToString = newBuilder() + .setFlags(flagsAsInt) + .setState(SampleDataClass.STATE_UNDEFINED) + .build() + .toString(); + assertThat(dataclassToString, containsString(flagsAsString)); + assertThat(dataclassToString, containsString("UNDEFINED")); + } + + @Test(expected = IllegalArgumentException.class) + public void testFlags_getValidated() { + newBuilder().setFlags(12345).build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testIntEnums_getValidated() { + newBuilder().setState(12345).build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testStringEnums_getValidated() { + newBuilder().setStateName("foo").build(); + } + + @Test(expected = IllegalStateException.class) + public void testCustomValidation_isTriggered() { + newBuilder().setNum2(-1).setNum4(1).build(); + } + + @Test + public void testLazyInit_isLazilyCalledOnce() { + assertNull(mSpecimen.mTmpStorage); + + int[] tmpStorage = mSpecimen.getTmpStorage(); + assertNotNull(tmpStorage); + assertSame(tmpStorage, mSpecimen.mTmpStorage); + + int[] tmpStorageAgain = mSpecimen.getTmpStorage(); + assertSame(tmpStorage, tmpStorageAgain); + } + + @Test(expected = IllegalStateException.class) + public void testCustomAnnotationValidation_isRun() { + newBuilder().setDayOfWeek(42).build(); + } + + @Test + public void testDataStructures_parcelCorrectly() { + SampleWithCustomBuilder otherParcelable = new SampleWithCustomBuilder.Builder().setDelay(3, SECONDS).build(); + + ParcelAllTheThingsDataClass instance = new ParcelAllTheThingsDataClass.Builder() + .setIntArray(40, 41) + .addMap("foo", otherParcelable) + .setSparseArray(new SparseArray<SampleWithCustomBuilder>() {{ + put(45, otherParcelable); + }}) + .setSparseIntArray(new SparseIntArray() {{ + put(48, 49); + }}) + .addStringMap("foo2", "fooValue") + .setStringArray("foo", "bar") + .addStringList("foo") + .build(); + + ParcelAllTheThingsDataClass unparceledInstance = + parcelAndUnparcel(instance, ParcelAllTheThingsDataClass.CREATOR); + + // SparseArray and friends don't implement equals + // so just compare string representations instead + assertEquals(instance.toString(), unparceledInstance.toString()); + } + + @Test + public void testNestedDataClasses_notMangledWhenParceled() { + assertEqualsAfterParcelling( + new SampleWithNestedDataClasses.NestedDataClass("1"), + SampleWithNestedDataClasses.NestedDataClass.CREATOR); + + assertEqualsAfterParcelling( + new SampleWithNestedDataClasses.NestedDataClass2("2"), + SampleWithNestedDataClasses.NestedDataClass2.CREATOR); + + assertEqualsAfterParcelling( + new SampleWithNestedDataClasses.NestedDataClass2.NestedDataClass3(3), + SampleWithNestedDataClasses.NestedDataClass2.NestedDataClass3.CREATOR); + } + + private static <T extends Parcelable> void assertEqualsAfterParcelling( + T p, Parcelable.Creator<T> creator) { + assertEquals(p, parcelAndUnparcel(p, creator)); + } + + private static <T extends Parcelable> T parcelAndUnparcel( + T original, Parcelable.Creator<T> creator) { + Parcel p = Parcel.obtain(); + try { + original.writeToParcel(p, 0); + p.setDataPosition(0); + return creator.createFromParcel(p); + } finally { + p.recycle(); + } + } +} diff --git a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java new file mode 100644 index 000000000000..d9fe1fd5a465 --- /dev/null +++ b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.codegentest; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemClock; + +import com.android.internal.util.DataClass; + +import java.util.concurrent.TimeUnit; + +@DataClass(genBuilder = true, genAidl = false, genToString = true) +public class SampleWithCustomBuilder implements Parcelable { + + long delayAmount = 0; + @NonNull + TimeUnit delayUnit = TimeUnit.MILLISECONDS; + + long creationTimestamp = SystemClock.uptimeMillis(); + + /** + * You can declare a class named {@code BaseBuilder} to have the generated builder extend from + * it instead. + * + * Same rules apply where defining a non-abstract method will suppress the generation of a + * method with the same signature. + * + * For abstract generatable methods, implementations are generated as normal, but original + * visibility is used, allowing you to hide methods. + * + * Here for example, we hide {@link #setDelayUnit} and {@link #setDelayAmount} from public API, + * replacing it with {@link #setDelay} instead. + */ + // Suppress setter generation for a field that is not supposed to come from user input. + @DataClass.Suppress("setCreationTimestamp") + static abstract class BaseBuilder { + + /** + * Hide methods by declaring them with reduced (package-private) visibility. + */ + abstract Builder setDelayAmount(long value); + + /** + * Alternatively, hide methods by using @hide, to hide them from public API only. + * + * @hide + */ + public abstract Builder setDelayUnit(TimeUnit value); + + /** + * Can provide additional method on the builder, e.g. as a replacement for the ones we've + * just hidden. + */ + public Builder setDelay(long amount, TimeUnit unit) { + setDelayAmount(amount); + setDelayUnit(unit); + return (Builder) this; + } + } + + + private static TimeUnit unparcelDelayUnit(Parcel p) { + return TimeUnit.values()[p.readInt()]; + } + + private void parcelDelayUnit(Parcel p, int flags) { + p.writeInt(delayUnit.ordinal()); + } + + + + // Code below generated by codegen v1.0.15. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + /* package-private */ SampleWithCustomBuilder( + long delayAmount, + @NonNull TimeUnit delayUnit, + long creationTimestamp) { + this.delayAmount = delayAmount; + this.delayUnit = delayUnit; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, delayUnit); + this.creationTimestamp = creationTimestamp; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public long getDelayAmount() { + return delayAmount; + } + + @DataClass.Generated.Member + public @NonNull TimeUnit getDelayUnit() { + return delayUnit; + } + + @DataClass.Generated.Member + public long getCreationTimestamp() { + return creationTimestamp; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "SampleWithCustomBuilder { " + + "delayAmount = " + delayAmount + ", " + + "delayUnit = " + delayUnit + ", " + + "creationTimestamp = " + creationTimestamp + + " }"; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeLong(delayAmount); + parcelDelayUnit(dest, flags); + dest.writeLong(creationTimestamp); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + protected SampleWithCustomBuilder(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + long _delayAmount = in.readLong(); + TimeUnit _delayUnit = unparcelDelayUnit(in); + long _creationTimestamp = in.readLong(); + + this.delayAmount = _delayAmount; + this.delayUnit = _delayUnit; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, delayUnit); + this.creationTimestamp = _creationTimestamp; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<SampleWithCustomBuilder> CREATOR + = new Parcelable.Creator<SampleWithCustomBuilder>() { + @Override + public SampleWithCustomBuilder[] newArray(int size) { + return new SampleWithCustomBuilder[size]; + } + + @Override + public SampleWithCustomBuilder createFromParcel(@NonNull Parcel in) { + return new SampleWithCustomBuilder(in); + } + }; + + /** + * A builder for {@link SampleWithCustomBuilder} + */ + @SuppressWarnings("WeakerAccess") + @DataClass.Generated.Member + public static class Builder extends BaseBuilder { + + private long delayAmount; + private @NonNull TimeUnit delayUnit; + private long creationTimestamp; + + private long mBuilderFieldsSet = 0L; + + public Builder() { + } + + @DataClass.Generated.Member + @Override + @NonNull Builder setDelayAmount(long value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + delayAmount = value; + return this; + } + + @DataClass.Generated.Member + @Override + public @NonNull Builder setDelayUnit(@NonNull TimeUnit value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + delayUnit = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull SampleWithCustomBuilder build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x8; // Mark builder used + + if ((mBuilderFieldsSet & 0x1) == 0) { + delayAmount = 0; + } + if ((mBuilderFieldsSet & 0x2) == 0) { + delayUnit = TimeUnit.MILLISECONDS; + } + if ((mBuilderFieldsSet & 0x4) == 0) { + creationTimestamp = SystemClock.uptimeMillis(); + } + SampleWithCustomBuilder o = new SampleWithCustomBuilder( + delayAmount, + delayUnit, + creationTimestamp); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x8) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } + + @DataClass.Generated( + time = 1582685648622L, + codegenVersion = "1.0.15", + sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java", + inputSignatures = " long delayAmount\n @android.annotation.NonNull java.util.concurrent.TimeUnit delayUnit\n long creationTimestamp\nprivate static java.util.concurrent.TimeUnit unparcelDelayUnit(android.os.Parcel)\nprivate void parcelDelayUnit(android.os.Parcel,int)\nclass SampleWithCustomBuilder extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)\nabstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayAmount(long)\npublic abstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayUnit(java.util.concurrent.TimeUnit)\npublic com.android.codegentest.SampleWithCustomBuilder.Builder setDelay(long,java.util.concurrent.TimeUnit)\nclass BaseBuilder extends java.lang.Object implements []") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java b/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java new file mode 100644 index 000000000000..f98d7b05c2d3 --- /dev/null +++ b/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.codegentest; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +/** + * An example of deeply nested data classes + */ +public class SampleWithNestedDataClasses { + + int mFoo = 0; + + @DataClass(genEqualsHashCode = true) + public static class NestedDataClass implements Parcelable { + + @NonNull String mBar; + + + + // Code below generated by codegen v1.0.15. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + public NestedDataClass( + @NonNull String bar) { + this.mBar = bar; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mBar); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public @NonNull String getBar() { + return mBar; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@android.annotation.Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(NestedDataClass other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + NestedDataClass that = (NestedDataClass) o; + //noinspection PointlessBooleanExpression + return true + && java.util.Objects.equals(mBar, that.mBar); + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + java.util.Objects.hashCode(mBar); + return _hash; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeString(mBar); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + protected NestedDataClass(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + String bar = in.readString(); + + this.mBar = bar; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mBar); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<NestedDataClass> CREATOR + = new Parcelable.Creator<NestedDataClass>() { + @Override + public NestedDataClass[] newArray(int size) { + return new NestedDataClass[size]; + } + + @Override + public NestedDataClass createFromParcel(@NonNull Parcel in) { + return new NestedDataClass(in); + } + }; + + @DataClass.Generated( + time = 1582685653406L, + codegenVersion = "1.0.15", + sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java", + inputSignatures = " @android.annotation.NonNull java.lang.String mBar\nclass NestedDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + + } + + @DataClass(genEqualsHashCode = true) + public static class NestedDataClass2 implements Parcelable { + + @NonNull String mBaz; + + @DataClass(genEqualsHashCode = true) + public static class NestedDataClass3 implements Parcelable { + + @NonNull long mBaz2; + + + + // Code below generated by codegen v1.0.15. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + public NestedDataClass3( + @NonNull long baz2) { + this.mBaz2 = baz2; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mBaz2); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public @NonNull long getBaz2() { + return mBaz2; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@android.annotation.Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(NestedDataClass3 other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + NestedDataClass3 that = (NestedDataClass3) o; + //noinspection PointlessBooleanExpression + return true + && mBaz2 == that.mBaz2; + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + Long.hashCode(mBaz2); + return _hash; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeLong(mBaz2); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + protected NestedDataClass3(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + long baz2 = in.readLong(); + + this.mBaz2 = baz2; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mBaz2); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<NestedDataClass3> CREATOR + = new Parcelable.Creator<NestedDataClass3>() { + @Override + public NestedDataClass3[] newArray(int size) { + return new NestedDataClass3[size]; + } + + @Override + public NestedDataClass3 createFromParcel(@NonNull Parcel in) { + return new NestedDataClass3(in); + } + }; + + @DataClass.Generated( + time = 1582685653415L, + codegenVersion = "1.0.15", + sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java", + inputSignatures = " @android.annotation.NonNull long mBaz2\nclass NestedDataClass3 extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + + } + + + + // Code below generated by codegen v1.0.15. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + public NestedDataClass2( + @NonNull String baz) { + this.mBaz = baz; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mBaz); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public @NonNull String getBaz() { + return mBaz; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@android.annotation.Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(NestedDataClass2 other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + NestedDataClass2 that = (NestedDataClass2) o; + //noinspection PointlessBooleanExpression + return true + && java.util.Objects.equals(mBaz, that.mBaz); + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + java.util.Objects.hashCode(mBaz); + return _hash; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeString(mBaz); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + protected NestedDataClass2(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + String baz = in.readString(); + + this.mBaz = baz; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mBaz); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<NestedDataClass2> CREATOR + = new Parcelable.Creator<NestedDataClass2>() { + @Override + public NestedDataClass2[] newArray(int size) { + return new NestedDataClass2[size]; + } + + @Override + public NestedDataClass2 createFromParcel(@NonNull Parcel in) { + return new NestedDataClass2(in); + } + }; + + @DataClass.Generated( + time = 1582685653420L, + codegenVersion = "1.0.15", + sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java", + inputSignatures = " @android.annotation.NonNull java.lang.String mBaz\nclass NestedDataClass2 extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + + } + + void someCode() {} +} diff --git a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java new file mode 100644 index 000000000000..6b4fc2fa157f --- /dev/null +++ b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.codegentest; + +import android.annotation.NonNull; + +import com.android.internal.util.DataClass; + +/** + * Test for some false positive pitfalls for + * {@link android.processor.staledataclass.StaleDataclassProcessor} + * + * Relies on the detector being run, failing the build should any of things here falsely + * register as stale. + */ +@DataClass(genConstructor = false, genBuilder = false) +public class StaleDataclassDetectorFalsePositivesTest { + + /** Interfaces should be ignored */ + public interface SomeListener { + void onEvent(); + } + + /** Enums should be ignored */ + private enum SomeEnum { ONE, TWO } + + /** Annotations should be ignored */ + public @interface SomeAnnotation {} + + /* Static initializers should be ignored */ + static {} + + /* Initializers should be ignored */ + {} + + /** Unrelated methods should be noted, without triggering staleness false positives */ + public @NonNull String someMethod(int param) { return null; } + + + + // Code below generated by codegen v1.0.15. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated( + time = 1582685652436L, + codegenVersion = "1.0.15", + sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java", + inputSignatures = "public @android.annotation.NonNull java.lang.String someMethod(int)\nclass StaleDataclassDetectorFalsePositivesTest extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/tests/Compatibility/Android.bp b/tests/Compatibility/Android.bp index 4ca406eba3cf..7dc44fa4a260 100644 --- a/tests/Compatibility/Android.bp +++ b/tests/Compatibility/Android.bp @@ -19,4 +19,7 @@ android_test { srcs: ["src/**/*.java"], platform_apis: true, certificate: "platform", + test_suites: [ + "csuite" + ], } diff --git a/tests/DozeTest/src/com/android/dreams/dozetest/DozeTestDream.java b/tests/DozeTest/src/com/android/dreams/dozetest/DozeTestDream.java index f4f610b1b280..fa292bd0d57a 100644 --- a/tests/DozeTest/src/com/android/dreams/dozetest/DozeTestDream.java +++ b/tests/DozeTest/src/com/android/dreams/dozetest/DozeTestDream.java @@ -100,7 +100,6 @@ public class DozeTestDream extends DreamService { public void onAttachedToWindow() { super.onAttachedToWindow(); setInteractive(false); - setLowProfile(true); setFullscreen(true); setContentView(R.layout.dream); setScreenBright(false); diff --git a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java index db2f659c655b..883c172e4990 100644 --- a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java +++ b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java @@ -27,8 +27,8 @@ import android.os.SystemClock; import android.util.EventLog; import android.util.EventLog.Event; -import androidx.test.InstrumentationRegistry; import androidx.test.filters.LargeTest; +import androidx.test.platform.app.InstrumentationRegistry; import dalvik.system.DexClassLoader; @@ -91,7 +91,7 @@ public final class DynamicCodeLoggerIntegrationTests { @BeforeClass public static void setUpAll() { - sContext = InstrumentationRegistry.getTargetContext(); + sContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); sMyUid = android.os.Process.myUid(); } @@ -331,7 +331,7 @@ public final class DynamicCodeLoggerIntegrationTests { // Abstract out the logic for running a native code loading test multiple times if needed and // leaving time for audit messages to reach the log. - private abstract class TestNativeCodeWithRetries { + private abstract static class TestNativeCodeWithRetries { String mExpectedContentHash; String mExpectedNameHash; diff --git a/tests/FeatureSplit/feature1/Android.bp b/tests/FeatureSplit/feature1/Android.bp index 1a93e842cec5..706a4f544393 100644 --- a/tests/FeatureSplit/feature1/Android.bp +++ b/tests/FeatureSplit/feature1/Android.bp @@ -18,7 +18,7 @@ android_test { name: "FeatureSplit1", srcs: ["**/*.java"], sdk_version: "current", - libs: ["FeatureSplitBase"], + libs: ["FeatureSplitBase", "FeatureSplit2"], aaptflags: [ "--package-id", "0x80", diff --git a/tests/FeatureSplit/feature1/AndroidManifest.xml b/tests/FeatureSplit/feature1/AndroidManifest.xml index b87361faac62..086c2c33422d 100644 --- a/tests/FeatureSplit/feature1/AndroidManifest.xml +++ b/tests/FeatureSplit/feature1/AndroidManifest.xml @@ -19,6 +19,7 @@ featureSplit="feature1"> <uses-sdk android:minSdkVersion="21" /> + <uses-split android:name="feature2" /> <application> <activity android:name=".one.One" android:label="Feature One"> diff --git a/tests/FeatureSplit/feature1/res/layout/included.xml b/tests/FeatureSplit/feature1/res/layout/included.xml index c64bdb7ff85f..f0c56f872438 100644 --- a/tests/FeatureSplit/feature1/res/layout/included.xml +++ b/tests/FeatureSplit/feature1/res/layout/included.xml @@ -2,4 +2,5 @@ <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/text" android:layout_width="wrap_content" - android:layout_height="wrap_content" /> + android:layout_height="wrap_content" + android:text="@string/feature2_string" /> diff --git a/tests/FeatureSplit/feature1/res/values/values.xml b/tests/FeatureSplit/feature1/res/values/values.xml index 7d58865b546e..6a840df2c79c 100644 --- a/tests/FeatureSplit/feature1/res/values/values.xml +++ b/tests/FeatureSplit/feature1/res/values/values.xml @@ -20,7 +20,8 @@ <integer name="test_integer2">200</integer> <color name="test_color2">#00ff00</color> <string-array name="string_array2"> - <item>@string/app_title</item> + <item>@string/app_title</item> + <item>@string/feature2_string</item> </string-array> </resources> diff --git a/tests/FeatureSplit/feature2/res/values/values.xml b/tests/FeatureSplit/feature2/res/values/values.xml index af5ed1b79b26..70e772c0d019 100644 --- a/tests/FeatureSplit/feature2/res/values/values.xml +++ b/tests/FeatureSplit/feature2/res/values/values.xml @@ -15,10 +15,11 @@ --> <resources> + <string name="feature2_string">feature 2 string referenced from feature 1</string> <integer name="test_integer3">300</integer> <color name="test_color3">#0000ff</color> <string-array name="string_array3"> - <item>@string/app_title</item> + <item>@string/app_title</item> </string-array> </resources> diff --git a/tests/FlickerTests/AndroidManifest.xml b/tests/FlickerTests/AndroidManifest.xml index 5b1a36b84cc4..91fb7c12b392 100644 --- a/tests/FlickerTests/AndroidManifest.xml +++ b/tests/FlickerTests/AndroidManifest.xml @@ -21,8 +21,12 @@ <!-- Read and write traces from external storage --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <!-- Write secure settings --> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <!-- Capture screen contents --> <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" /> + <!-- Enable / Disable tracing !--> + <uses-permission android:name="android.permission.DUMP" /> <!-- Run layers trace --> <uses-permission android:name="android.permission.HARDWARE_TEST"/> <application> @@ -33,4 +37,4 @@ android:targetPackage="com.android.server.wm.flicker" android:label="WindowManager Flicker Tests"> </instrumentation> -</manifest>
\ No newline at end of file +</manifest> diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml index e36f97656f2a..d1da47f0f9d8 100644 --- a/tests/FlickerTests/AndroidTest.xml +++ b/tests/FlickerTests/AndroidTest.xml @@ -25,5 +25,6 @@ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> <option name="directory-keys" value="/sdcard/flicker" /> <option name="collect-on-run-ended-only" value="true" /> + <option name="clean-up" value="true" /> </metrics_collector> </configuration> diff --git a/tests/FlickerTests/TEST_MAPPING b/tests/FlickerTests/TEST_MAPPING index 55a61471dfb8..db251b907caa 100644 --- a/tests/FlickerTests/TEST_MAPPING +++ b/tests/FlickerTests/TEST_MAPPING @@ -1,5 +1,11 @@ { "postsubmit": [ + // Run tests on real device + { + "name": "FlickerTests", + "keywords": ["primary-device"] + }, + // Also run the tests in the cloud { "name": "FlickerTests" } diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/Assertions.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/Assertions.java deleted file mode 100644 index 84f9f871324c..000000000000 --- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/Assertions.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker; - -import java.util.concurrent.TimeUnit; -import java.util.function.Function; - -/** - * Collection of functional interfaces and classes representing assertions and their associated - * results. Assertions are functions that are applied over a single trace entry and returns a - * result which includes a detailed reason if the assertion fails. - */ -class Assertions { - /** - * Checks assertion on a single trace entry. - * - * @param <T> trace entry type to perform the assertion on. - */ - @FunctionalInterface - interface TraceAssertion<T> extends Function<T, Result> { - /** - * Returns an assertion that represents the logical negation of this assertion. - * - * @return a assertion that represents the logical negation of this assertion - */ - default TraceAssertion<T> negate() { - return (T t) -> apply(t).negate(); - } - } - - /** - * Checks assertion on a single layers trace entry. - */ - @FunctionalInterface - interface LayersTraceAssertion extends TraceAssertion<LayersTrace.Entry> { - - } - - /** - * Utility class to store assertions with an identifier to help generate more useful debug - * data when dealing with multiple assertions. - */ - static class NamedAssertion<T> { - final TraceAssertion<T> assertion; - final String name; - - NamedAssertion(TraceAssertion<T> assertion, String name) { - this.assertion = assertion; - this.name = name; - } - } - - /** - * Contains the result of an assertion including the reason for failed assertions. - */ - static class Result { - static final String NEGATION_PREFIX = "!"; - final boolean success; - final long timestamp; - final String assertionName; - final String reason; - - Result(boolean success, long timestamp, String assertionName, String reason) { - this.success = success; - this.timestamp = timestamp; - this.assertionName = assertionName; - this.reason = reason; - } - - Result(boolean success, String reason) { - this.success = success; - this.reason = reason; - this.assertionName = ""; - this.timestamp = 0; - } - - /** - * Returns the negated {@code Result} and adds a negation prefix to the assertion name. - */ - Result negate() { - String negatedAssertionName; - if (this.assertionName.startsWith(NEGATION_PREFIX)) { - negatedAssertionName = this.assertionName.substring(NEGATION_PREFIX.length() + 1); - } else { - negatedAssertionName = NEGATION_PREFIX + this.assertionName; - } - return new Result(!this.success, this.timestamp, negatedAssertionName, this.reason); - } - - boolean passed() { - return this.success; - } - - boolean failed() { - return !this.success; - } - - @Override - public String toString() { - return "Timestamp: " + prettyTimestamp(timestamp) - + "\nAssertion: " + assertionName - + "\nReason: " + reason; - } - - private String prettyTimestamp(long timestamp_ns) { - StringBuilder prettyTimestamp = new StringBuilder(); - TimeUnit[] timeUnits = {TimeUnit.HOURS, TimeUnit.MINUTES, TimeUnit.SECONDS, TimeUnit - .MILLISECONDS}; - String[] unitSuffixes = {"h", "m", "s", "ms"}; - - for (int i = 0; i < timeUnits.length; i++) { - long convertedTime = timeUnits[i].convert(timestamp_ns, TimeUnit.NANOSECONDS); - timestamp_ns -= TimeUnit.NANOSECONDS.convert(convertedTime, timeUnits[i]); - prettyTimestamp.append(convertedTime).append(unitSuffixes[i]); - } - - return prettyTimestamp.toString(); - } - } -} diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AssertionsChecker.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AssertionsChecker.java deleted file mode 100644 index 3c65d3c341b3..000000000000 --- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AssertionsChecker.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker; - -import com.android.server.wm.flicker.Assertions.NamedAssertion; -import com.android.server.wm.flicker.Assertions.Result; -import com.android.server.wm.flicker.Assertions.TraceAssertion; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.stream.Collectors; - -/** - * Captures some of the common logic in {@link LayersTraceSubject} and {@link WmTraceSubject} - * used to filter trace entries and combine multiple assertions. - * - * @param <T> trace entry type - */ -public class AssertionsChecker<T extends ITraceEntry> { - private boolean mFilterEntriesByRange = false; - private long mFilterStartTime = 0; - private long mFilterEndTime = 0; - private AssertionOption mOption = AssertionOption.NONE; - private List<NamedAssertion<T>> mAssertions = new LinkedList<>(); - - void add(Assertions.TraceAssertion<T> assertion, String name) { - mAssertions.add(new NamedAssertion<>(assertion, name)); - } - - void filterByRange(long startTime, long endTime) { - mFilterEntriesByRange = true; - mFilterStartTime = startTime; - mFilterEndTime = endTime; - } - - private void setOption(AssertionOption option) { - if (mOption != AssertionOption.NONE && option != mOption) { - throw new IllegalArgumentException("Cannot use " + mOption + " option with " - + option + " option."); - } - mOption = option; - } - - public void checkFirstEntry() { - setOption(AssertionOption.CHECK_FIRST_ENTRY); - } - - public void checkLastEntry() { - setOption(AssertionOption.CHECK_LAST_ENTRY); - } - - public void checkChangingAssertions() { - setOption(AssertionOption.CHECK_CHANGING_ASSERTIONS); - } - - - /** - * Filters trace entries then runs assertions returning a list of failures. - * - * @param entries list of entries to perform assertions on - * @return list of failed assertion results - */ - List<Result> test(List<T> entries) { - List<T> filteredEntries; - List<Result> failures; - - if (mFilterEntriesByRange) { - filteredEntries = entries.stream() - .filter(e -> ((e.getTimestamp() >= mFilterStartTime) - && (e.getTimestamp() <= mFilterEndTime))) - .collect(Collectors.toList()); - } else { - filteredEntries = entries; - } - - switch (mOption) { - case CHECK_CHANGING_ASSERTIONS: - return assertChanges(filteredEntries); - case CHECK_FIRST_ENTRY: - return assertEntry(filteredEntries.get(0)); - case CHECK_LAST_ENTRY: - return assertEntry(filteredEntries.get(filteredEntries.size() - 1)); - } - return assertAll(filteredEntries); - } - - /** - * Steps through each trace entry checking if provided assertions are true in the order they - * are added. Each assertion must be true for at least a single trace entry. - * - * This can be used to check for asserting a change in property over a trace. Such as visibility - * for a window changes from true to false or top-most window changes from A to Bb and back to A - * again. - */ - private List<Result> assertChanges(List<T> entries) { - List<Result> failures = new ArrayList<>(); - int entryIndex = 0; - int assertionIndex = 0; - int lastPassedAssertionIndex = -1; - - if (mAssertions.size() == 0) { - return failures; - } - - while (assertionIndex < mAssertions.size() && entryIndex < entries.size()) { - TraceAssertion<T> currentAssertion = mAssertions.get(assertionIndex).assertion; - Result result = currentAssertion.apply(entries.get(entryIndex)); - if (result.passed()) { - lastPassedAssertionIndex = assertionIndex; - entryIndex++; - continue; - } - - if (lastPassedAssertionIndex != assertionIndex) { - failures.add(result); - break; - } - assertionIndex++; - - if (assertionIndex == mAssertions.size()) { - failures.add(result); - break; - } - } - - if (failures.isEmpty()) { - if (assertionIndex != mAssertions.size() - 1) { - String reason = "\nAssertion " + mAssertions.get(assertionIndex).name - + " never became false"; - reason += "\nPassed assertions: " + mAssertions.stream().limit(assertionIndex) - .map(assertion -> assertion.name).collect(Collectors.joining(",")); - reason += "\nUntested assertions: " + mAssertions.stream().skip(assertionIndex + 1) - .map(assertion -> assertion.name).collect(Collectors.joining(",")); - - Result result = new Result(false /* success */, 0 /* timestamp */, - "assertChanges", "Not all assertions passed." + reason); - failures.add(result); - } - } - return failures; - } - - private List<Result> assertEntry(T entry) { - List<Result> failures = new ArrayList<>(); - for (NamedAssertion<T> assertion : mAssertions) { - Result result = assertion.assertion.apply(entry); - if (result.failed()) { - failures.add(result); - } - } - return failures; - } - - private List<Result> assertAll(List<T> entries) { - return mAssertions.stream().flatMap( - assertion -> entries.stream() - .map(assertion.assertion) - .filter(Result::failed)) - .collect(Collectors.toList()); - } - - private enum AssertionOption { - NONE, - CHECK_CHANGING_ASSERTIONS, - CHECK_FIRST_ENTRY, - CHECK_LAST_ENTRY, - } -} diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AutomationUtils.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AutomationUtils.java deleted file mode 100644 index e00a2474556c..000000000000 --- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AutomationUtils.java +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker; - -import static android.os.SystemClock.sleep; -import static android.system.helpers.OverviewHelper.isRecentsInLauncher; -import static android.view.Surface.ROTATION_0; - -import static com.android.compatibility.common.util.SystemUtil.runShellCommand; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.fail; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.graphics.Point; -import android.graphics.Rect; -import android.os.RemoteException; -import android.support.test.launcherhelper.LauncherStrategyFactory; -import android.support.test.uiautomator.By; -import android.support.test.uiautomator.BySelector; -import android.support.test.uiautomator.Configurator; -import android.support.test.uiautomator.UiDevice; -import android.support.test.uiautomator.UiObject2; -import android.support.test.uiautomator.Until; -import android.util.Log; -import android.util.Rational; -import android.view.View; -import android.view.ViewConfiguration; - -import androidx.test.InstrumentationRegistry; - -/** - * Collection of UI Automation helper functions. - */ -public class AutomationUtils { - private static final String SYSTEMUI_PACKAGE = "com.android.systemui"; - private static final long FIND_TIMEOUT = 10000; - private static final long LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout() * 2L; - private static final String TAG = "FLICKER"; - - public static void wakeUpAndGoToHomeScreen() { - UiDevice device = UiDevice.getInstance(InstrumentationRegistry - .getInstrumentation()); - try { - device.wakeUp(); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - device.pressHome(); - } - - /** - * Sets {@link android.app.UiAutomation#waitForIdle(long, long)} global timeout to 0 causing - * the {@link android.app.UiAutomation#waitForIdle(long, long)} function to timeout instantly. - * This removes some delays when using the UIAutomator library required to create fast UI - * transitions. - */ - static void setFastWait() { - Configurator.getInstance().setWaitForIdleTimeout(0); - } - - /** - * Reverts {@link android.app.UiAutomation#waitForIdle(long, long)} to default behavior. - */ - static void setDefaultWait() { - Configurator.getInstance().setWaitForIdleTimeout(10000); - } - - public static boolean isQuickstepEnabled(UiDevice device) { - return device.findObject(By.res(SYSTEMUI_PACKAGE, "recent_apps")) == null; - } - - public static void openQuickstep(UiDevice device) { - if (isQuickstepEnabled(device)) { - int height = device.getDisplayHeight(); - UiObject2 navBar = device.findObject(By.res(SYSTEMUI_PACKAGE, "navigation_bar_frame")); - - Rect navBarVisibleBounds; - - // TODO(vishnun) investigate why this object cannot be found. - if (navBar != null) { - navBarVisibleBounds = navBar.getVisibleBounds(); - } else { - Log.e(TAG, "Could not find nav bar, infer location"); - navBarVisibleBounds = WindowUtils.getNavigationBarPosition(ROTATION_0); - } - - // Swipe from nav bar to 2/3rd down the screen. - device.swipe( - navBarVisibleBounds.centerX(), navBarVisibleBounds.centerY(), - navBarVisibleBounds.centerX(), height * 2 / 3, - (navBarVisibleBounds.centerY() - height * 2 / 3) / 100); // 100 px/step - } else { - try { - device.pressRecentApps(); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - BySelector RECENTS = By.res(SYSTEMUI_PACKAGE, "recents_view"); - - // use a long timeout to wait until recents populated - if (device.wait( - Until.findObject(isRecentsInLauncher() - ? getLauncherOverviewSelector(device) : RECENTS), - 10000) == null) { - fail("Recents didn't appear"); - } - device.waitForIdle(); - } - - static void clearRecents(UiDevice device) { - if (isQuickstepEnabled(device)) { - openQuickstep(device); - - for (int i = 0; i < 5; i++) { - device.swipe(device.getDisplayWidth() / 2, - device.getDisplayHeight() / 2, device.getDisplayWidth(), - device.getDisplayHeight() / 2, - 5); - - BySelector clearAllSelector = By.res("com.google.android.apps.nexuslauncher", - "clear_all_button"); - UiObject2 clearAllButton = device.wait(Until.findObject(clearAllSelector), 100); - if (clearAllButton != null) { - clearAllButton.click(); - return; - } - } - } - } - - private static BySelector getLauncherOverviewSelector(UiDevice device) { - return By.res(device.getLauncherPackageName(), "overview_panel"); - } - - private static void longPressRecents(UiDevice device) { - BySelector recentsSelector = By.res(SYSTEMUI_PACKAGE, "recent_apps"); - UiObject2 recentsButton = device.wait(Until.findObject(recentsSelector), FIND_TIMEOUT); - assertNotNull("Unable to find recents button", recentsButton); - recentsButton.click(LONG_PRESS_TIMEOUT); - } - - public static void launchSplitScreen(UiDevice device) { - String mLauncherPackage = LauncherStrategyFactory.getInstance(device) - .getLauncherStrategy().getSupportedLauncherPackage(); - - if (isQuickstepEnabled(device)) { - // Quickstep enabled - openQuickstep(device); - - BySelector overviewIconSelector = By.res(mLauncherPackage, "icon") - .clazz(View.class); - UiObject2 overviewIcon = device.wait(Until.findObject(overviewIconSelector), - FIND_TIMEOUT); - assertNotNull("Unable to find app icon in Overview", overviewIcon); - overviewIcon.click(); - - BySelector splitscreenButtonSelector = By.text("Split screen"); - UiObject2 splitscreenButton = device.wait(Until.findObject(splitscreenButtonSelector), - FIND_TIMEOUT); - assertNotNull("Unable to find Split screen button in Overview", splitscreenButton); - splitscreenButton.click(); - } else { - // Classic long press recents - longPressRecents(device); - } - // Wait for animation to complete. - sleep(2000); - } - - public static void exitSplitScreen(UiDevice device) { - if (isQuickstepEnabled(device)) { - // Quickstep enabled - BySelector dividerSelector = By.res(SYSTEMUI_PACKAGE, "docked_divider_handle"); - UiObject2 divider = device.wait(Until.findObject(dividerSelector), FIND_TIMEOUT); - assertNotNull("Unable to find Split screen divider", divider); - - // Drag the split screen divider to the top of the screen - divider.drag(new Point(device.getDisplayWidth() / 2, 0), 400); - } else { - // Classic long press recents - longPressRecents(device); - } - // Wait for animation to complete. - sleep(2000); - } - - static void resizeSplitScreen(UiDevice device, Rational windowHeightRatio) { - BySelector dividerSelector = By.res(SYSTEMUI_PACKAGE, "docked_divider_handle"); - UiObject2 divider = device.wait(Until.findObject(dividerSelector), FIND_TIMEOUT); - assertNotNull("Unable to find Split screen divider", divider); - int destHeight = - (int) (WindowUtils.getDisplayBounds().height() * windowHeightRatio.floatValue()); - // Drag the split screen divider to so that the ratio of top window height and bottom - // window height is windowHeightRatio - device.drag(divider.getVisibleBounds().centerX(), divider.getVisibleBounds().centerY(), - device.getDisplayWidth() / 2, destHeight, 10); - //divider.drag(new Point(device.getDisplayWidth() / 2, destHeight), 400) - divider = device.wait(Until.findObject(dividerSelector), FIND_TIMEOUT); - - // Wait for animation to complete. - sleep(2000); - } - - static void closePipWindow(UiDevice device) { - UiObject2 pipWindow = device.findObject( - By.res(SYSTEMUI_PACKAGE, "background")); - pipWindow.click(); - UiObject2 exitPipObject = device.findObject( - By.res(SYSTEMUI_PACKAGE, "dismiss")); - exitPipObject.click(); - // Wait for animation to complete. - sleep(2000); - } - - static void expandPipWindow(UiDevice device) { - UiObject2 pipWindow = device.findObject( - By.res(SYSTEMUI_PACKAGE, "background")); - pipWindow.click(); - pipWindow.click(); - } - - public static void stopPackage(Context context, String packageName) { - runShellCommand("am force-stop " + packageName); - int packageUid; - try { - packageUid = context.getPackageManager().getPackageUid(packageName, /* flags= */0); - } catch (PackageManager.NameNotFoundException e) { - return; - } - while (targetPackageIsRunning(packageUid)) { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - //ignore - } - } - } - - private static boolean targetPackageIsRunning(int uid) { - final String result = runShellCommand( - String.format("cmd activity get-uid-state %d", uid)); - return !result.contains("(NONEXISTENT)"); - } -}
\ No newline at end of file diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTrace.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTrace.java deleted file mode 100644 index 660ec0fe4833..000000000000 --- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTrace.java +++ /dev/null @@ -1,412 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker; - -import android.annotation.Nullable; -import android.graphics.Rect; -import android.surfaceflinger.nano.Layers.LayerProto; -import android.surfaceflinger.nano.Layers.RectProto; -import android.surfaceflinger.nano.Layers.RegionProto; -import android.surfaceflinger.nano.Layerstrace.LayersTraceFileProto; -import android.surfaceflinger.nano.Layerstrace.LayersTraceProto; -import android.util.SparseArray; - -import com.android.server.wm.flicker.Assertions.Result; - -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -/** - * Contains a collection of parsed Layers trace entries and assertions to apply over - * a single entry. - * - * Each entry is parsed into a list of {@link LayersTrace.Entry} objects. - */ -public class LayersTrace { - final private List<Entry> mEntries; - @Nullable - final private Path mSource; - - private LayersTrace(List<Entry> entries, Path source) { - this.mEntries = entries; - this.mSource = source; - } - - /** - * Parses {@code LayersTraceFileProto} from {@code data} and uses the proto to generates a list - * of trace entries, storing the flattened layers into its hierarchical structure. - * - * @param data binary proto data - * @param source Path to source of data for additional debug information - */ - static LayersTrace parseFrom(byte[] data, Path source) { - List<Entry> entries = new ArrayList<>(); - LayersTraceFileProto fileProto; - try { - fileProto = LayersTraceFileProto.parseFrom(data); - } catch (Exception e) { - throw new RuntimeException(e); - } - for (LayersTraceProto traceProto : fileProto.entry) { - Entry entry = Entry.fromFlattenedLayers(traceProto.elapsedRealtimeNanos, - traceProto.layers.layers); - entries.add(entry); - } - return new LayersTrace(entries, source); - } - - /** - * Parses {@code LayersTraceFileProto} from {@code data} and uses the proto to generates a list - * of trace entries, storing the flattened layers into its hierarchical structure. - * - * @param data binary proto data - */ - static LayersTrace parseFrom(byte[] data) { - return parseFrom(data, null); - } - - List<Entry> getEntries() { - return mEntries; - } - - Entry getEntry(long timestamp) { - Optional<Entry> entry = mEntries.stream() - .filter(e -> e.getTimestamp() == timestamp) - .findFirst(); - if (!entry.isPresent()) { - throw new RuntimeException("Entry does not exist for timestamp " + timestamp); - } - return entry.get(); - } - - Optional<Path> getSource() { - return Optional.ofNullable(mSource); - } - - /** - * Represents a single Layer trace entry. - */ - static class Entry implements ITraceEntry { - private long mTimestamp; - private List<Layer> mRootLayers; // hierarchical representation of layers - private List<Layer> mFlattenedLayers = null; - - private Entry(long timestamp, List<Layer> rootLayers) { - this.mTimestamp = timestamp; - this.mRootLayers = rootLayers; - } - - /** - * Constructs the layer hierarchy from a flattened list of layers. - */ - static Entry fromFlattenedLayers(long timestamp, LayerProto[] protos) { - SparseArray<Layer> layerMap = new SparseArray<>(); - ArrayList<Layer> orphans = new ArrayList<>(); - for (LayerProto proto : protos) { - int id = proto.id; - int parentId = proto.parent; - - Layer newLayer = layerMap.get(id); - if (newLayer == null) { - newLayer = new Layer(proto); - layerMap.append(id, newLayer); - } else if (newLayer.mProto != null) { - throw new RuntimeException("Duplicate layer id found:" + id); - } else { - newLayer.mProto = proto; - orphans.remove(newLayer); - } - - // add parent placeholder - if (layerMap.get(parentId) == null) { - Layer orphanLayer = new Layer(null); - layerMap.append(parentId, orphanLayer); - orphans.add(orphanLayer); - } - layerMap.get(parentId).addChild(newLayer); - newLayer.addParent(layerMap.get(parentId)); - } - - // Fail if we find orphan layers. - orphans.remove(layerMap.get(-1)); - orphans.forEach(orphan -> { - String childNodes = orphan.mChildren.stream().map(node -> - Integer.toString(node.getId())).collect(Collectors.joining(", ")); - int orphanId = orphan.mChildren.get(0).mProto.parent; - throw new RuntimeException( - "Failed to parse layers trace. Found orphan layers with parent " - + "layer id:" + orphanId + " : " + childNodes); - }); - - return new Entry(timestamp, layerMap.get(-1).mChildren); - } - - /** - * Extracts {@link Rect} from {@link RectProto}. - */ - private static Rect extract(RectProto proto) { - return new Rect(proto.left, proto.top, proto.right, proto.bottom); - } - - /** - * Extracts {@link Rect} from {@link RegionProto} by returning a rect that encompasses all - * the rects making up the region. - */ - private static Rect extract(RegionProto regionProto) { - Rect region = new Rect(); - for (RectProto proto : regionProto.rect) { - region.union(proto.left, proto.top, proto.right, proto.bottom); - } - return region; - } - - /** - * Checks if a region specified by {@code testRect} is covered by all visible layers. - */ - Result coversRegion(Rect testRect) { - String assertionName = "coversRegion"; - Collection<Layer> layers = asFlattenedLayers(); - - for (int x = testRect.left; x < testRect.right; x++) { - for (int y = testRect.top; y < testRect.bottom; y++) { - boolean emptyRegionFound = true; - for (Layer layer : layers) { - if (layer.isInvisible() || layer.isHiddenByParent()) { - continue; - } - for (RectProto rectProto : layer.mProto.visibleRegion.rect) { - Rect r = extract(rectProto); - if (r.contains(x, y)) { - y = r.bottom; - emptyRegionFound = false; - } - } - } - if (emptyRegionFound) { - String reason = "Region to test: " + testRect - + "\nfirst empty point: " + x + ", " + y; - reason += "\nvisible regions:"; - for (Layer layer : layers) { - if (layer.isInvisible() || layer.isHiddenByParent()) { - continue; - } - Rect r = extract(layer.mProto.visibleRegion); - reason += "\n" + layer.mProto.name + r.toString(); - } - return new Result(false /* success */, this.mTimestamp, assertionName, - reason); - } - } - } - String info = "Region covered: " + testRect; - return new Result(true /* success */, this.mTimestamp, assertionName, info); - } - - /** - * Checks if a layer with name {@code layerName} has a visible region - * {@code expectedVisibleRegion}. - */ - Result hasVisibleRegion(String layerName, Rect expectedVisibleRegion) { - String assertionName = "hasVisibleRegion"; - String reason = "Could not find " + layerName; - for (Layer layer : asFlattenedLayers()) { - if (layer.mProto.name.contains(layerName)) { - if (layer.isHiddenByParent()) { - reason = layer.getHiddenByParentReason(); - continue; - } - if (layer.isInvisible()) { - reason = layer.getVisibilityReason(); - continue; - } - Rect visibleRegion = extract(layer.mProto.visibleRegion); - if (visibleRegion.equals(expectedVisibleRegion)) { - return new Result(true /* success */, this.mTimestamp, assertionName, - layer.mProto.name + "has visible region " + expectedVisibleRegion); - } - reason = layer.mProto.name + " has visible region:" + visibleRegion + " " - + "expected:" + expectedVisibleRegion; - } - } - return new Result(false /* success */, this.mTimestamp, assertionName, reason); - } - - /** - * Checks if a layer with name {@code layerName} is visible. - */ - Result isVisible(String layerName) { - String assertionName = "isVisible"; - String reason = "Could not find " + layerName; - for (Layer layer : asFlattenedLayers()) { - if (layer.mProto.name.contains(layerName)) { - if (layer.isHiddenByParent()) { - reason = layer.getHiddenByParentReason(); - continue; - } - if (layer.isInvisible()) { - reason = layer.getVisibilityReason(); - continue; - } - return new Result(true /* success */, this.mTimestamp, assertionName, - layer.mProto.name + " is visible"); - } - } - return new Result(false /* success */, this.mTimestamp, assertionName, reason); - } - - @Override - public long getTimestamp() { - return mTimestamp; - } - - List<Layer> getRootLayers() { - return mRootLayers; - } - - List<Layer> asFlattenedLayers() { - if (mFlattenedLayers == null) { - mFlattenedLayers = new ArrayList<>(); - ArrayList<Layer> pendingLayers = new ArrayList<>(this.mRootLayers); - while (!pendingLayers.isEmpty()) { - Layer layer = pendingLayers.remove(0); - mFlattenedLayers.add(layer); - pendingLayers.addAll(layer.mChildren); - } - } - return mFlattenedLayers; - } - - Rect getVisibleBounds(String layerName) { - List<Layer> layers = asFlattenedLayers(); - for (Layer layer : layers) { - if (layer.mProto.name.contains(layerName) && layer.isVisible()) { - return extract(layer.mProto.visibleRegion); - } - } - return new Rect(0, 0, 0, 0); - } - } - - /** - * Represents a single layer with links to its parent and child layers. - */ - static class Layer { - @Nullable - LayerProto mProto; - List<Layer> mChildren; - @Nullable - Layer mParent = null; - - private Layer(LayerProto proto) { - this.mProto = proto; - this.mChildren = new ArrayList<>(); - } - - private void addChild(Layer childLayer) { - this.mChildren.add(childLayer); - } - - private void addParent(Layer parentLayer) { - this.mParent = parentLayer; - } - - int getId() { - return mProto.id; - } - - boolean isActiveBufferEmpty() { - return this.mProto.activeBuffer == null || this.mProto.activeBuffer.height == 0 - || this.mProto.activeBuffer.width == 0; - } - - boolean isVisibleRegionEmpty() { - if (this.mProto.visibleRegion == null) { - return true; - } - Rect visibleRect = Entry.extract(this.mProto.visibleRegion); - return visibleRect.height() == 0 || visibleRect.width() == 0; - } - - boolean isHidden() { - return (this.mProto.flags & /* FLAG_HIDDEN */ 0x1) != 0x0; - } - - boolean isVisible() { - return (!isActiveBufferEmpty() || isColorLayer()) && - !isHidden() && this.mProto.color.a > 0 && !isVisibleRegionEmpty(); - } - - boolean isColorLayer() { - return this.mProto.type.equals("ColorLayer"); - } - - boolean isRootLayer() { - return mParent == null || mParent.mProto == null; - } - - boolean isInvisible() { - return !isVisible(); - } - - boolean isHiddenByParent() { - return !isRootLayer() && (mParent.isHidden() || mParent.isHiddenByParent()); - } - - String getHiddenByParentReason() { - String reason = "Layer " + mProto.name; - if (isHiddenByParent()) { - reason += " is hidden by parent: " + mParent.mProto.name; - } else { - reason += " is not hidden by parent: " + mParent.mProto.name; - } - return reason; - } - - String getVisibilityReason() { - String reason = "Layer " + mProto.name; - if (isVisible()) { - reason += " is visible:"; - } else { - reason += " is invisible:"; - if (this.mProto.activeBuffer == null) { - reason += " activeBuffer=null"; - } else if (this.mProto.activeBuffer.height == 0) { - reason += " activeBuffer.height=0"; - } else if (this.mProto.activeBuffer.width == 0) { - reason += " activeBuffer.width=0"; - } - if (!isColorLayer()) { - reason += " type != ColorLayer"; - } - if (isHidden()) { - reason += " flags=" + this.mProto.flags + " (FLAG_HIDDEN set)"; - } - if (this.mProto.color.a == 0) { - reason += " color.a=0"; - } - if (isVisibleRegionEmpty()) { - reason += " visible region is empty"; - } - } - return reason; - } - } -}
\ No newline at end of file diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTraceSubject.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTraceSubject.java deleted file mode 100644 index b4c97e4fc4f2..000000000000 --- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTraceSubject.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker; - -import static com.google.common.truth.Truth.assertAbout; -import static com.google.common.truth.Truth.assertWithMessage; - -import android.annotation.Nullable; -import android.graphics.Rect; - -import com.android.server.wm.flicker.Assertions.Result; -import com.android.server.wm.flicker.LayersTrace.Entry; -import com.android.server.wm.flicker.TransitionRunner.TransitionResult; - -import com.google.common.truth.FailureStrategy; -import com.google.common.truth.Subject; -import com.google.common.truth.SubjectFactory; - -import java.util.List; -import java.util.stream.Collectors; - -/** - * Truth subject for {@link LayersTrace} objects. - */ -public class LayersTraceSubject extends Subject<LayersTraceSubject, LayersTrace> { - // Boiler-plate Subject.Factory for LayersTraceSubject - private static final SubjectFactory<LayersTraceSubject, LayersTrace> FACTORY = - new SubjectFactory<LayersTraceSubject, LayersTrace>() { - @Override - public LayersTraceSubject getSubject( - FailureStrategy fs, @Nullable LayersTrace target) { - return new LayersTraceSubject(fs, target); - } - }; - - private AssertionsChecker<Entry> mChecker = new AssertionsChecker<>(); - - private LayersTraceSubject(FailureStrategy fs, @Nullable LayersTrace subject) { - super(fs, subject); - } - - // User-defined entry point - public static LayersTraceSubject assertThat(@Nullable LayersTrace entry) { - return assertAbout(FACTORY).that(entry); - } - - // User-defined entry point - public static LayersTraceSubject assertThat(@Nullable TransitionResult result) { - LayersTrace entries = LayersTrace.parseFrom(result.getLayersTrace(), - result.getLayersTracePath()); - return assertWithMessage(result.toString()).about(FACTORY).that(entries); - } - - // Static method for getting the subject factory (for use with assertAbout()) - public static SubjectFactory<LayersTraceSubject, LayersTrace> entries() { - return FACTORY; - } - - public void forAllEntries() { - test(); - } - - public void forRange(long startTime, long endTime) { - mChecker.filterByRange(startTime, endTime); - test(); - } - - public LayersTraceSubject then() { - mChecker.checkChangingAssertions(); - return this; - } - - public void inTheBeginning() { - if (getSubject().getEntries().isEmpty()) { - fail("No entries found."); - } - mChecker.checkFirstEntry(); - test(); - } - - public void atTheEnd() { - if (getSubject().getEntries().isEmpty()) { - fail("No entries found."); - } - mChecker.checkLastEntry(); - test(); - } - - private void test() { - List<Result> failures = mChecker.test(getSubject().getEntries()); - if (!failures.isEmpty()) { - String failureLogs = failures.stream().map(Result::toString) - .collect(Collectors.joining("\n")); - String tracePath = ""; - if (getSubject().getSource().isPresent()) { - tracePath = "\nLayers Trace can be found in: " - + getSubject().getSource().get().toAbsolutePath() + "\n"; - } - fail(tracePath + failureLogs); - } - } - - public LayersTraceSubject coversRegion(Rect rect) { - mChecker.add(entry -> entry.coversRegion(rect), - "coversRegion(" + rect + ")"); - return this; - } - - public LayersTraceSubject hasVisibleRegion(String layerName, Rect size) { - mChecker.add(entry -> entry.hasVisibleRegion(layerName, size), - "hasVisibleRegion(" + layerName + size + ")"); - return this; - } - - public LayersTraceSubject showsLayer(String layerName) { - mChecker.add(entry -> entry.isVisible(layerName), - "showsLayer(" + layerName + ")"); - return this; - } - - public LayersTraceSubject hidesLayer(String layerName) { - mChecker.add(entry -> entry.isVisible(layerName).negate(), - "hidesLayer(" + layerName + ")"); - return this; - } -} diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/TransitionRunner.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/TransitionRunner.java deleted file mode 100644 index 0a3fe3c00de2..000000000000 --- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/TransitionRunner.java +++ /dev/null @@ -1,424 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker; - -import android.annotation.Nullable; -import android.support.annotation.VisibleForTesting; -import android.util.Log; - -import androidx.test.InstrumentationRegistry; - -import com.android.server.wm.flicker.monitor.ITransitionMonitor; -import com.android.server.wm.flicker.monitor.LayersTraceMonitor; -import com.android.server.wm.flicker.monitor.ScreenRecorder; -import com.android.server.wm.flicker.monitor.WindowAnimationFrameStatsMonitor; -import com.android.server.wm.flicker.monitor.WindowManagerTraceMonitor; - -import com.google.common.io.Files; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; - -/** - * Builds and runs UI transitions capturing test artifacts. - * - * User can compose a transition from simpler steps, specifying setup and teardown steps. During - * a transition, Layers trace, WindowManager trace, screen recordings and window animation frame - * stats can be captured. - * - * <pre> - * Transition builder options: - * {@link TransitionBuilder#run(Runnable)} run transition under test. Monitors will be started - * before the transition and stopped after the transition is completed. - * {@link TransitionBuilder#repeat(int)} repeat transitions under test multiple times recording - * result for each run. - * {@link TransitionBuilder#withTag(String)} specify a string identifier used to prefix logs and - * artifacts generated. - * {@link TransitionBuilder#runBeforeAll(Runnable)} run setup transitions once before all other - * transition are run to set up an initial state on device. - * {@link TransitionBuilder#runBefore(Runnable)} run setup transitions before each test transition - * run. - * {@link TransitionBuilder#runAfter(Runnable)} run teardown transitions after each test - * transition. - * {@link TransitionBuilder#runAfter(Runnable)} run teardown transitions once after all - * other transition are run. - * {@link TransitionBuilder#includeJankyRuns()} disables {@link WindowAnimationFrameStatsMonitor} - * to monitor janky frames. If janky frames are detected, then the test run is skipped. This - * monitor is enabled by default. - * {@link TransitionBuilder#skipLayersTrace()} disables {@link LayersTraceMonitor} used to - * capture Layers trace during a transition. This monitor is enabled by default. - * {@link TransitionBuilder#skipWindowManagerTrace()} disables {@link WindowManagerTraceMonitor} - * used to capture WindowManager trace during a transition. This monitor is enabled by - * default. - * {@link TransitionBuilder#recordAllRuns()} records the screen contents and saves it to a file. - * All the runs including setup and teardown transitions are included in the recording. This - * monitor is used for debugging purposes. - * {@link TransitionBuilder#recordEachRun()} records the screen contents during test transitions - * and saves it to a file for each run. This monitor is used for debugging purposes. - * - * Example transition to capture WindowManager and Layers trace when opening a test app: - * {@code - * TransitionRunner.newBuilder() - * .withTag("OpenTestAppFast") - * .runBeforeAll(UiAutomationLib::wakeUp) - * .runBeforeAll(UiAutomationLib::UnlockDevice) - * .runBeforeAll(UiAutomationLib::openTestApp) - * .runBefore(UiAutomationLib::closeTestApp) - * .run(UiAutomationLib::openTestApp) - * .runAfterAll(UiAutomationLib::closeTestApp) - * .repeat(5) - * .build() - * .run(); - * } - * </pre> - */ -class TransitionRunner { - private static final String TAG = "FLICKER"; - private final ScreenRecorder mScreenRecorder; - private final WindowManagerTraceMonitor mWmTraceMonitor; - private final LayersTraceMonitor mLayersTraceMonitor; - private final WindowAnimationFrameStatsMonitor mFrameStatsMonitor; - - private final List<ITransitionMonitor> mAllRunsMonitors; - private final List<ITransitionMonitor> mPerRunMonitors; - private final List<Runnable> mBeforeAlls; - private final List<Runnable> mBefores; - private final List<Runnable> mTransitions; - private final List<Runnable> mAfters; - private final List<Runnable> mAfterAlls; - - private final int mIterations; - private final String mTestTag; - - @Nullable - private List<TransitionResult> mResults = null; - - private TransitionRunner(TransitionBuilder builder) { - mScreenRecorder = builder.mScreenRecorder; - mWmTraceMonitor = builder.mWmTraceMonitor; - mLayersTraceMonitor = builder.mLayersTraceMonitor; - mFrameStatsMonitor = builder.mFrameStatsMonitor; - - mAllRunsMonitors = builder.mAllRunsMonitors; - mPerRunMonitors = builder.mPerRunMonitors; - mBeforeAlls = builder.mBeforeAlls; - mBefores = builder.mBefores; - mTransitions = builder.mTransitions; - mAfters = builder.mAfters; - mAfterAlls = builder.mAfterAlls; - - mIterations = builder.mIterations; - mTestTag = builder.mTestTag; - } - - static TransitionBuilder newBuilder() { - return new TransitionBuilder(); - } - - /** - * Runs the composed transition and calls monitors at the appropriate stages. If jank monitor - * is enabled, transitions with jank are skipped. - * - * @return itself - */ - TransitionRunner run() { - mResults = new ArrayList<>(); - mAllRunsMonitors.forEach(ITransitionMonitor::start); - mBeforeAlls.forEach(Runnable::run); - for (int iteration = 0; iteration < mIterations; iteration++) { - mBefores.forEach(Runnable::run); - mPerRunMonitors.forEach(ITransitionMonitor::start); - mTransitions.forEach(Runnable::run); - mPerRunMonitors.forEach(ITransitionMonitor::stop); - mAfters.forEach(Runnable::run); - if (runJankFree() && mFrameStatsMonitor.jankyFramesDetected()) { - String msg = String.format("Skipping iteration %d/%d for test %s due to jank. %s", - iteration, mIterations - 1, mTestTag, mFrameStatsMonitor.toString()); - Log.e(TAG, msg); - continue; - } - mResults.add(saveResult(iteration)); - } - mAfterAlls.forEach(Runnable::run); - mAllRunsMonitors.forEach(monitor -> { - monitor.stop(); - Path path = monitor.save(mTestTag); - Log.e(TAG, "Video saved to " + path.toString()); - }); - return this; - } - - /** - * Returns a list of transition results. - * - * @return list of transition results. - */ - List<TransitionResult> getResults() { - if (mResults == null) { - throw new IllegalStateException("Results do not exist!"); - } - return mResults; - } - - /** - * Deletes all transition results that are not marked for saving. - * - * @return list of transition results. - */ - void deleteResults() { - if (mResults == null) { - return; - } - mResults.stream() - .filter(TransitionResult::canDelete) - .forEach(TransitionResult::delete); - mResults = null; - } - - /** - * Saves monitor results to file. - * - * @return object containing paths to test artifacts - */ - private TransitionResult saveResult(int iteration) { - Path windowTrace = null; - Path layerTrace = null; - Path screenCaptureVideo = null; - - if (mPerRunMonitors.contains(mWmTraceMonitor)) { - windowTrace = mWmTraceMonitor.save(mTestTag, iteration); - } - if (mPerRunMonitors.contains(mLayersTraceMonitor)) { - layerTrace = mLayersTraceMonitor.save(mTestTag, iteration); - } - if (mPerRunMonitors.contains(mScreenRecorder)) { - screenCaptureVideo = mScreenRecorder.save(mTestTag, iteration); - } - return new TransitionResult(layerTrace, windowTrace, screenCaptureVideo); - } - - private boolean runJankFree() { - return mPerRunMonitors.contains(mFrameStatsMonitor); - } - - public String getTestTag() { - return mTestTag; - } - - /** - * Stores paths to all test artifacts. - */ - @VisibleForTesting - public static class TransitionResult { - @Nullable - final Path layersTrace; - @Nullable - final Path windowManagerTrace; - @Nullable - final Path screenCaptureVideo; - private boolean flaggedForSaving; - - TransitionResult(@Nullable Path layersTrace, @Nullable Path windowManagerTrace, - @Nullable Path screenCaptureVideo) { - this.layersTrace = layersTrace; - this.windowManagerTrace = windowManagerTrace; - this.screenCaptureVideo = screenCaptureVideo; - } - - void flagForSaving() { - flaggedForSaving = true; - } - - boolean canDelete() { - return !flaggedForSaving; - } - - boolean layersTraceExists() { - return layersTrace != null && layersTrace.toFile().exists(); - } - - byte[] getLayersTrace() { - try { - return Files.toByteArray(this.layersTrace.toFile()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - Path getLayersTracePath() { - return layersTrace; - } - - boolean windowManagerTraceExists() { - return windowManagerTrace != null && windowManagerTrace.toFile().exists(); - } - - public byte[] getWindowManagerTrace() { - try { - return Files.toByteArray(this.windowManagerTrace.toFile()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - Path getWindowManagerTracePath() { - return windowManagerTrace; - } - - boolean screenCaptureVideoExists() { - return screenCaptureVideo != null && screenCaptureVideo.toFile().exists(); - } - - Path screenCaptureVideoPath() { - return screenCaptureVideo; - } - - void delete() { - if (layersTraceExists()) layersTrace.toFile().delete(); - if (windowManagerTraceExists()) windowManagerTrace.toFile().delete(); - if (screenCaptureVideoExists()) screenCaptureVideo.toFile().delete(); - } - } - - /** - * Builds a {@link TransitionRunner} instance. - */ - static class TransitionBuilder { - private ScreenRecorder mScreenRecorder; - private WindowManagerTraceMonitor mWmTraceMonitor; - private LayersTraceMonitor mLayersTraceMonitor; - private WindowAnimationFrameStatsMonitor mFrameStatsMonitor; - - private List<ITransitionMonitor> mAllRunsMonitors = new LinkedList<>(); - private List<ITransitionMonitor> mPerRunMonitors = new LinkedList<>(); - private List<Runnable> mBeforeAlls = new LinkedList<>(); - private List<Runnable> mBefores = new LinkedList<>(); - private List<Runnable> mTransitions = new LinkedList<>(); - private List<Runnable> mAfters = new LinkedList<>(); - private List<Runnable> mAfterAlls = new LinkedList<>(); - - private boolean mRunJankFree = true; - private boolean mCaptureWindowManagerTrace = true; - private boolean mCaptureLayersTrace = true; - private boolean mRecordEachRun = false; - private int mIterations = 1; - private String mTestTag = ""; - - private boolean mRecordAllRuns = false; - - TransitionBuilder() { - mScreenRecorder = new ScreenRecorder(); - mWmTraceMonitor = new WindowManagerTraceMonitor(); - mLayersTraceMonitor = new LayersTraceMonitor(); - mFrameStatsMonitor = new - WindowAnimationFrameStatsMonitor(InstrumentationRegistry.getInstrumentation()); - } - - TransitionRunner build() { - if (mCaptureWindowManagerTrace) { - mPerRunMonitors.add(mWmTraceMonitor); - } - - if (mCaptureLayersTrace) { - mPerRunMonitors.add(mLayersTraceMonitor); - } - - if (mRunJankFree) { - mPerRunMonitors.add(mFrameStatsMonitor); - } - - if (mRecordAllRuns) { - mAllRunsMonitors.add(mScreenRecorder); - } - - if (mRecordEachRun) { - mPerRunMonitors.add(mScreenRecorder); - } - - return new TransitionRunner(this); - } - - TransitionBuilder runBeforeAll(Runnable runnable) { - mBeforeAlls.add(runnable); - return this; - } - - TransitionBuilder runBefore(Runnable runnable) { - mBefores.add(runnable); - return this; - } - - TransitionBuilder run(Runnable runnable) { - mTransitions.add(runnable); - return this; - } - - TransitionBuilder runAfter(Runnable runnable) { - mAfters.add(runnable); - return this; - } - - TransitionBuilder runAfterAll(Runnable runnable) { - mAfterAlls.add(runnable); - return this; - } - - TransitionBuilder repeat(int iterations) { - mIterations = iterations; - return this; - } - - TransitionBuilder skipWindowManagerTrace() { - mCaptureWindowManagerTrace = false; - return this; - } - - TransitionBuilder skipLayersTrace() { - mCaptureLayersTrace = false; - return this; - } - - TransitionBuilder includeJankyRuns() { - mRunJankFree = false; - return this; - } - - TransitionBuilder recordEachRun() { - if (mRecordAllRuns) { - throw new IllegalArgumentException("Invalid option with recordAllRuns"); - } - mRecordEachRun = true; - return this; - } - - TransitionBuilder recordAllRuns() { - if (mRecordEachRun) { - throw new IllegalArgumentException("Invalid option with recordEachRun"); - } - mRecordAllRuns = true; - return this; - } - - TransitionBuilder withTag(String testTag) { - mTestTag = testTag; - return this; - } - } -} diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowManagerTrace.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowManagerTrace.java deleted file mode 100644 index e3592eb8cd01..000000000000 --- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowManagerTrace.java +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker; - -import android.annotation.Nullable; - -import com.android.server.wm.flicker.Assertions.Result; -import com.android.server.wm.nano.AppWindowTokenProto; -import com.android.server.wm.nano.StackProto; -import com.android.server.wm.nano.TaskProto; -import com.android.server.wm.nano.WindowManagerTraceFileProto; -import com.android.server.wm.nano.WindowManagerTraceProto; -import com.android.server.wm.nano.WindowStateProto; -import com.android.server.wm.nano.WindowTokenProto; - -import com.google.protobuf.nano.InvalidProtocolBufferNanoException; - -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -/** - * Contains a collection of parsed WindowManager trace entries and assertions to apply over - * a single entry. - * - * Each entry is parsed into a list of {@link WindowManagerTrace.Entry} objects. - */ -public class WindowManagerTrace { - private static final int DEFAULT_DISPLAY = 0; - private final List<Entry> mEntries; - @Nullable - final private Path mSource; - - private WindowManagerTrace(List<Entry> entries, Path source) { - this.mEntries = entries; - this.mSource = source; - } - - /** - * Parses {@code WindowManagerTraceFileProto} from {@code data} and uses the proto to - * generates a list of trace entries. - * - * @param data binary proto data - * @param source Path to source of data for additional debug information - */ - static WindowManagerTrace parseFrom(byte[] data, Path source) { - List<Entry> entries = new ArrayList<>(); - - WindowManagerTraceFileProto fileProto; - try { - fileProto = WindowManagerTraceFileProto.parseFrom(data); - } catch (InvalidProtocolBufferNanoException e) { - throw new RuntimeException(e); - } - for (WindowManagerTraceProto entryProto : fileProto.entry) { - entries.add(new Entry(entryProto)); - } - return new WindowManagerTrace(entries, source); - } - - static WindowManagerTrace parseFrom(byte[] data) { - return parseFrom(data, null); - } - - public List<Entry> getEntries() { - return mEntries; - } - - Entry getEntry(long timestamp) { - Optional<Entry> entry = mEntries.stream() - .filter(e -> e.getTimestamp() == timestamp) - .findFirst(); - if (!entry.isPresent()) { - throw new RuntimeException("Entry does not exist for timestamp " + timestamp); - } - return entry.get(); - } - - Optional<Path> getSource() { - return Optional.ofNullable(mSource); - } - - /** - * Represents a single WindowManager trace entry. - */ - static class Entry implements ITraceEntry { - private final WindowManagerTraceProto mProto; - - Entry(WindowManagerTraceProto proto) { - mProto = proto; - } - - private static Result isWindowVisible(String windowTitle, - WindowTokenProto[] windowTokenProtos) { - boolean titleFound = false; - for (WindowTokenProto windowToken : windowTokenProtos) { - for (WindowStateProto windowState : windowToken.windows) { - if (windowState.identifier.title.contains(windowTitle)) { - titleFound = true; - if (isVisible(windowState)) { - return new Result(true /* success */, - windowState.identifier.title + " is visible"); - } - } - } - } - - String reason; - if (!titleFound) { - reason = windowTitle + " cannot be found"; - } else { - reason = windowTitle + " is invisible"; - } - return new Result(false /* success */, reason); - } - - private static boolean isVisible(WindowStateProto windowState) { - return windowState.windowContainer.visible; - } - - @Override - public long getTimestamp() { - return mProto.elapsedRealtimeNanos; - } - - /** - * Returns window title of the top most visible app window. - */ - private String getTopVisibleAppWindow() { - StackProto[] stacks = mProto.windowManagerService.rootWindowContainer - .displays[DEFAULT_DISPLAY].stacks; - for (StackProto stack : stacks) { - for (TaskProto task : stack.tasks) { - for (AppWindowTokenProto token : task.appWindowTokens) { - for (WindowStateProto windowState : token.windowToken.windows) { - if (windowState.windowContainer.visible) { - return task.appWindowTokens[0].name; - } - } - } - } - } - - return ""; - } - - /** - * Checks if aboveAppWindow with {@code windowTitle} is visible. - */ - Result isAboveAppWindowVisible(String windowTitle) { - WindowTokenProto[] windowTokenProtos = mProto.windowManagerService - .rootWindowContainer - .displays[DEFAULT_DISPLAY].aboveAppWindows; - Result result = isWindowVisible(windowTitle, windowTokenProtos); - return new Result(result.success, getTimestamp(), "showsAboveAppWindow", result.reason); - } - - /** - * Checks if belowAppWindow with {@code windowTitle} is visible. - */ - Result isBelowAppWindowVisible(String windowTitle) { - WindowTokenProto[] windowTokenProtos = mProto.windowManagerService - .rootWindowContainer - .displays[DEFAULT_DISPLAY].belowAppWindows; - Result result = isWindowVisible(windowTitle, windowTokenProtos); - return new Result(result.success, getTimestamp(), "isBelowAppWindowVisible", - result.reason); - } - - /** - * Checks if imeWindow with {@code windowTitle} is visible. - */ - Result isImeWindowVisible(String windowTitle) { - WindowTokenProto[] windowTokenProtos = mProto.windowManagerService - .rootWindowContainer - .displays[DEFAULT_DISPLAY].imeWindows; - Result result = isWindowVisible(windowTitle, windowTokenProtos); - return new Result(result.success, getTimestamp(), "isImeWindowVisible", - result.reason); - } - - /** - * Checks if app window with {@code windowTitle} is on top. - */ - Result isVisibleAppWindowOnTop(String windowTitle) { - String topAppWindow = getTopVisibleAppWindow(); - boolean success = topAppWindow.contains(windowTitle); - String reason = "wanted=" + windowTitle + " found=" + topAppWindow; - return new Result(success, getTimestamp(), "isAppWindowOnTop", reason); - } - - /** - * Checks if app window with {@code windowTitle} is visible. - */ - Result isAppWindowVisible(String windowTitle) { - final String assertionName = "isAppWindowVisible"; - boolean titleFound = false; - StackProto[] stacks = mProto.windowManagerService.rootWindowContainer - .displays[DEFAULT_DISPLAY].stacks; - for (StackProto stack : stacks) { - for (TaskProto task : stack.tasks) { - for (AppWindowTokenProto token : task.appWindowTokens) { - if (token.name.contains(windowTitle)) { - titleFound = true; - for (WindowStateProto windowState : token.windowToken.windows) { - if (windowState.windowContainer.visible) { - return new Result(true /* success */, getTimestamp(), - assertionName, "Window " + token.name + - "is visible"); - } - } - } - } - } - } - String reason; - if (!titleFound) { - reason = "Window " + windowTitle + " cannot be found"; - } else { - reason = "Window " + windowTitle + " is invisible"; - } - return new Result(false /* success */, getTimestamp(), assertionName, reason); - } - } -}
\ No newline at end of file diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowUtils.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowUtils.java deleted file mode 100644 index c54396f895e4..000000000000 --- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowUtils.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Point; -import android.graphics.Rect; -import android.view.Surface; -import android.view.WindowManager; - -import androidx.test.InstrumentationRegistry; - -/** - * Helper functions to retrieve system window sizes and positions. - */ -class WindowUtils { - - static Rect getDisplayBounds() { - Point display = new Point(); - WindowManager wm = - (WindowManager) InstrumentationRegistry.getContext().getSystemService( - Context.WINDOW_SERVICE); - wm.getDefaultDisplay().getRealSize(display); - return new Rect(0, 0, display.x, display.y); - } - - private static int getCurrentRotation() { - WindowManager wm = - (WindowManager) InstrumentationRegistry.getContext().getSystemService( - Context.WINDOW_SERVICE); - return wm.getDefaultDisplay().getRotation(); - } - - static Rect getDisplayBounds(int requestedRotation) { - Rect displayBounds = getDisplayBounds(); - int currentDisplayRotation = getCurrentRotation(); - - boolean displayIsRotated = (currentDisplayRotation == Surface.ROTATION_90 || - currentDisplayRotation == Surface.ROTATION_270); - - boolean requestedDisplayIsRotated = requestedRotation == Surface.ROTATION_90 || - requestedRotation == Surface.ROTATION_270; - - // if the current orientation changes with the requested rotation, - // flip height and width of display bounds. - if (displayIsRotated != requestedDisplayIsRotated) { - return new Rect(0, 0, displayBounds.height(), displayBounds.width()); - } - - return new Rect(0, 0, displayBounds.width(), displayBounds.height()); - } - - - static Rect getAppPosition(int requestedRotation) { - Rect displayBounds = getDisplayBounds(); - int currentDisplayRotation = getCurrentRotation(); - - boolean displayIsRotated = currentDisplayRotation == Surface.ROTATION_90 || - currentDisplayRotation == Surface.ROTATION_270; - - boolean requestedAppIsRotated = requestedRotation == Surface.ROTATION_90 || - requestedRotation == Surface.ROTATION_270; - - // display size will change if the display is reflected. Flip height and width of app if the - // requested rotation is different from the current rotation. - if (displayIsRotated != requestedAppIsRotated) { - return new Rect(0, 0, displayBounds.height(), displayBounds.width()); - } - - return new Rect(0, 0, displayBounds.width(), displayBounds.height()); - } - - static Rect getStatusBarPosition(int requestedRotation) { - Resources resources = InstrumentationRegistry.getContext().getResources(); - String resourceName; - Rect displayBounds = getDisplayBounds(); - int width; - if (requestedRotation == Surface.ROTATION_0 || requestedRotation == Surface.ROTATION_180) { - resourceName = "status_bar_height_portrait"; - width = Math.min(displayBounds.width(), displayBounds.height()); - } else { - resourceName = "status_bar_height_landscape"; - width = Math.max(displayBounds.width(), displayBounds.height()); - } - - int resourceId = resources.getIdentifier(resourceName, "dimen", "android"); - int height = resources.getDimensionPixelSize(resourceId); - - return new Rect(0, 0, width, height); - } - - static Rect getNavigationBarPosition(int requestedRotation) { - Resources resources = InstrumentationRegistry.getContext().getResources(); - Rect displayBounds = getDisplayBounds(); - int displayWidth = Math.min(displayBounds.width(), displayBounds.height()); - int displayHeight = Math.max(displayBounds.width(), displayBounds.height()); - int resourceId; - if (requestedRotation == Surface.ROTATION_0 || requestedRotation == Surface.ROTATION_180) { - resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android"); - int height = resources.getDimensionPixelSize(resourceId); - return new Rect(0, displayHeight - height, displayWidth, displayHeight); - } else { - resourceId = resources.getIdentifier("navigation_bar_width", "dimen", "android"); - int width = resources.getDimensionPixelSize(resourceId); - // swap display dimensions in landscape or seascape mode - int temp = displayHeight; - displayHeight = displayWidth; - displayWidth = temp; - if (requestedRotation == Surface.ROTATION_90) { - return new Rect(0, 0, width, displayHeight); - } else { - return new Rect(displayWidth - width, 0, displayWidth, displayHeight); - } - } - } - - static int getNavigationBarHeight() { - Resources resources = InstrumentationRegistry.getContext().getResources(); - int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android"); - return resources.getDimensionPixelSize(resourceId); - } - - static int getDockedStackDividerInset() { - Resources resources = InstrumentationRegistry.getContext().getResources(); - int resourceId = resources.getIdentifier("docked_stack_divider_insets", "dimen", - "android"); - return resources.getDimensionPixelSize(resourceId); - } -} diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WmTraceSubject.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WmTraceSubject.java deleted file mode 100644 index 1fc7d591d2bb..000000000000 --- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WmTraceSubject.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker; - -import static com.google.common.truth.Truth.assertAbout; -import static com.google.common.truth.Truth.assertWithMessage; - -import android.annotation.Nullable; - -import com.android.server.wm.flicker.Assertions.Result; -import com.android.server.wm.flicker.TransitionRunner.TransitionResult; - -import com.google.common.truth.FailureStrategy; -import com.google.common.truth.Subject; -import com.google.common.truth.SubjectFactory; - -import java.nio.file.Path; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -/** - * Truth subject for {@link WindowManagerTrace} objects. - */ -public class WmTraceSubject extends Subject<WmTraceSubject, WindowManagerTrace> { - // Boiler-plate Subject.Factory for WmTraceSubject - private static final SubjectFactory<WmTraceSubject, WindowManagerTrace> FACTORY = - new SubjectFactory<WmTraceSubject, WindowManagerTrace>() { - @Override - public WmTraceSubject getSubject( - FailureStrategy fs, @Nullable WindowManagerTrace target) { - return new WmTraceSubject(fs, target); - } - }; - - private AssertionsChecker<WindowManagerTrace.Entry> mChecker = new AssertionsChecker<>(); - - private WmTraceSubject(FailureStrategy fs, @Nullable WindowManagerTrace subject) { - super(fs, subject); - } - - // User-defined entry point - public static WmTraceSubject assertThat(@Nullable WindowManagerTrace entry) { - return assertAbout(FACTORY).that(entry); - } - - // User-defined entry point - public static WmTraceSubject assertThat(@Nullable TransitionResult result) { - WindowManagerTrace entries = WindowManagerTrace.parseFrom(result.getWindowManagerTrace(), - result.getWindowManagerTracePath()); - return assertWithMessage(result.toString()).about(FACTORY).that(entries); - } - - // Static method for getting the subject factory (for use with assertAbout()) - public static SubjectFactory<WmTraceSubject, WindowManagerTrace> entries() { - return FACTORY; - } - - public void forAllEntries() { - test(); - } - - public void forRange(long startTime, long endTime) { - mChecker.filterByRange(startTime, endTime); - test(); - } - - public WmTraceSubject then() { - mChecker.checkChangingAssertions(); - return this; - } - - public void inTheBeginning() { - if (getSubject().getEntries().isEmpty()) { - fail("No entries found."); - } - mChecker.checkFirstEntry(); - test(); - } - - public void atTheEnd() { - if (getSubject().getEntries().isEmpty()) { - fail("No entries found."); - } - mChecker.checkLastEntry(); - test(); - } - - private void test() { - List<Result> failures = mChecker.test(getSubject().getEntries()); - if (!failures.isEmpty()) { - Optional<Path> failureTracePath = getSubject().getSource(); - String failureLogs = failures.stream().map(Result::toString) - .collect(Collectors.joining("\n")); - String tracePath = ""; - if (failureTracePath.isPresent()) { - tracePath = "\nWindowManager Trace can be found in: " - + failureTracePath.get().toAbsolutePath() + "\n"; - } - fail(tracePath + failureLogs); - } - } - - public WmTraceSubject showsAboveAppWindow(String partialWindowTitle) { - mChecker.add(entry -> entry.isAboveAppWindowVisible(partialWindowTitle), - "showsAboveAppWindow(" + partialWindowTitle + ")"); - return this; - } - - public WmTraceSubject hidesAboveAppWindow(String partialWindowTitle) { - mChecker.add(entry -> entry.isAboveAppWindowVisible(partialWindowTitle).negate(), - "hidesAboveAppWindow" + "(" + partialWindowTitle + ")"); - return this; - } - - public WmTraceSubject showsBelowAppWindow(String partialWindowTitle) { - mChecker.add(entry -> entry.isBelowAppWindowVisible(partialWindowTitle), - "showsBelowAppWindow(" + partialWindowTitle + ")"); - return this; - } - - public WmTraceSubject hidesBelowAppWindow(String partialWindowTitle) { - mChecker.add(entry -> entry.isBelowAppWindowVisible(partialWindowTitle).negate(), - "hidesBelowAppWindow" + "(" + partialWindowTitle + ")"); - return this; - } - - public WmTraceSubject showsImeWindow(String partialWindowTitle) { - mChecker.add(entry -> entry.isImeWindowVisible(partialWindowTitle), - "showsBelowAppWindow(" + partialWindowTitle + ")"); - return this; - } - - public WmTraceSubject hidesImeWindow(String partialWindowTitle) { - mChecker.add(entry -> entry.isImeWindowVisible(partialWindowTitle).negate(), - "hidesImeWindow" + "(" + partialWindowTitle + ")"); - return this; - } - - public WmTraceSubject showsAppWindowOnTop(String partialWindowTitle) { - mChecker.add( - entry -> { - Result result = entry.isAppWindowVisible(partialWindowTitle); - if (result.passed()) { - result = entry.isVisibleAppWindowOnTop(partialWindowTitle); - } - return result; - }, - "showsAppWindowOnTop(" + partialWindowTitle + ")" - ); - return this; - } - - public WmTraceSubject hidesAppWindowOnTop(String partialWindowTitle) { - mChecker.add( - entry -> { - Result result = entry.isAppWindowVisible(partialWindowTitle).negate(); - if (result.failed()) { - result = entry.isVisibleAppWindowOnTop(partialWindowTitle).negate(); - } - return result; - }, - "hidesAppWindowOnTop(" + partialWindowTitle + ")" - ); - return this; - } - - public WmTraceSubject showsAppWindow(String partialWindowTitle) { - mChecker.add(entry -> entry.isAppWindowVisible(partialWindowTitle), - "showsAppWindow(" + partialWindowTitle + ")"); - return this; - } - - public WmTraceSubject hidesAppWindow(String partialWindowTitle) { - mChecker.add(entry -> entry.isAppWindowVisible(partialWindowTitle).negate(), - "hidesAppWindow(" + partialWindowTitle + ")"); - return this; - } -} diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ITransitionMonitor.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ITransitionMonitor.java deleted file mode 100644 index 67e0ecc1cde7..000000000000 --- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ITransitionMonitor.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker.monitor; - -import android.os.Environment; - -import java.nio.file.Path; -import java.nio.file.Paths; - -/** - * Collects test artifacts during a UI transition. - */ -public interface ITransitionMonitor { - Path OUTPUT_DIR = Paths.get(Environment.getExternalStorageDirectory().toString(), "flicker"); - - /** - * Starts monitor. - */ - void start(); - - /** - * Stops monitor. - */ - void stop(); - - /** - * Saves any monitor artifacts to file adding {@code testTag} and {@code iteration} - * to the file name. - * - * @param testTag suffix added to artifact name - * @param iteration suffix added to artifact name - * - * @return Path to saved artifact - */ - default Path save(String testTag, int iteration) { - return save(testTag + "_" + iteration); - } - - /** - * Saves any monitor artifacts to file adding {@code testTag} to the file name. - * - * @param testTag suffix added to artifact name - * - * @return Path to saved artifact - */ - default Path save(String testTag) { - throw new UnsupportedOperationException("Save not implemented for this monitor"); - } -} diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.java deleted file mode 100644 index c55d068b41b8..000000000000 --- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker.monitor; - -import android.os.IBinder; -import android.os.Parcel; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.util.Log; - -/** - * Captures Layers trace from SurfaceFlinger. - */ -public class LayersTraceMonitor extends TraceMonitor { - private static final String TAG = "LayersTraceMonitor"; - private IBinder mSurfaceFlinger = ServiceManager.getService("SurfaceFlinger"); - - public LayersTraceMonitor() { - traceFileName = "layers_trace.pb"; - } - - @Override - public void start() { - setEnabled(true); - } - - @Override - public void stop() { - setEnabled(false); - } - - @Override - public boolean isEnabled() throws RemoteException { - Parcel data = Parcel.obtain(); - Parcel reply = Parcel.obtain(); - data.writeInterfaceToken("android.ui.ISurfaceComposer"); - mSurfaceFlinger.transact(/* LAYER_TRACE_STATUS_CODE */ 1026, - data, reply, 0 /* flags */); - return reply.readBoolean(); - } - - private void setEnabled(boolean isEnabled) { - Parcel data = null; - try { - if (mSurfaceFlinger != null) { - data = Parcel.obtain(); - data.writeInterfaceToken("android.ui.ISurfaceComposer"); - data.writeInt(isEnabled ? 1 : 0); - mSurfaceFlinger.transact( /* LAYER_TRACE_CONTROL_CODE */ 1025, - data, null, 0 /* flags */); - } - } catch (RemoteException e) { - Log.e(TAG, "Could not set layer tracing." + e.toString()); - } finally { - if (data != null) { - data.recycle(); - } - } - } -} diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ScreenRecorder.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ScreenRecorder.java deleted file mode 100644 index 4787586777ae..000000000000 --- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ScreenRecorder.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker.monitor; - -import static com.android.compatibility.common.util.SystemUtil.runShellCommand; - -import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; - -import android.support.annotation.VisibleForTesting; -import android.util.Log; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -/** - * Captures screen contents and saves it as a mp4 video file. - */ -public class ScreenRecorder implements ITransitionMonitor { - @VisibleForTesting - static final Path DEFAULT_OUTPUT_PATH = OUTPUT_DIR.resolve("transition.mp4"); - private static final String TAG = "FLICKER"; - private Thread recorderThread; - - @VisibleForTesting - static Path getPath(String testTag) { - return OUTPUT_DIR.resolve(testTag + ".mp4"); - } - - @Override - public void start() { - OUTPUT_DIR.toFile().mkdirs(); - String command = "screenrecord " + DEFAULT_OUTPUT_PATH; - recorderThread = new Thread(() -> { - try { - Runtime.getRuntime().exec(command); - } catch (IOException e) { - Log.e(TAG, "Error executing " + command, e); - } - }); - recorderThread.start(); - } - - @Override - public void stop() { - runShellCommand("killall -s 2 screenrecord"); - try { - recorderThread.join(); - } catch (InterruptedException e) { - // ignore - } - } - - @Override - public Path save(String testTag) { - try { - return Files.move(DEFAULT_OUTPUT_PATH, getPath(testTag), - REPLACE_EXISTING); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/TraceMonitor.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/TraceMonitor.java deleted file mode 100644 index 0e154ecd5d4d..000000000000 --- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/TraceMonitor.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker.monitor; - -import static com.android.compatibility.common.util.SystemUtil.runShellCommand; - -import android.os.RemoteException; - -import com.android.internal.annotations.VisibleForTesting; - -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Locale; - -/** - * Base class for monitors containing common logic to read the trace - * as a byte array and save the trace to another location. - */ -public abstract class TraceMonitor implements ITransitionMonitor { - public static final String TAG = "FLICKER"; - private static final String TRACE_DIR = "/data/misc/wmtrace/"; - - String traceFileName; - - abstract boolean isEnabled() throws RemoteException; - - /** - * Saves trace file to the external storage directory suffixing the name with the testtag - * and iteration. - * - * Moves the trace file from the default location via a shell command since the test app - * does not have security privileges to access /data/misc/wmtrace. - * - * @param testTag suffix added to trace name used to identify trace - * - * @return Path to saved trace file - */ - @Override - public Path save(String testTag) { - OUTPUT_DIR.toFile().mkdirs(); - Path traceFileCopy = getOutputTraceFilePath(testTag); - String copyCommand = String.format(Locale.getDefault(), "mv %s%s %s", TRACE_DIR, - traceFileName, traceFileCopy.toString()); - runShellCommand(copyCommand); - return traceFileCopy; - } - - @VisibleForTesting - Path getOutputTraceFilePath(String testTag) { - return OUTPUT_DIR.resolve(traceFileName + "_" + testTag); - } -} diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitor.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitor.java deleted file mode 100644 index 3f86f0d001d7..000000000000 --- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitor.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker.monitor; - -import static android.view.FrameStats.UNDEFINED_TIME_NANO; - -import android.app.Instrumentation; -import android.util.Log; -import android.view.FrameStats; - -/** - * Monitors {@link android.view.WindowAnimationFrameStats} to detect janky frames. - * - * Adapted from {@link androidx.test.jank.internal.WindowAnimationFrameStatsMonitorImpl} - * using the same threshold to determine jank. - */ -public class WindowAnimationFrameStatsMonitor implements ITransitionMonitor { - - private static final String TAG = "FLICKER"; - // Maximum normalized error in frame duration before the frame is considered janky - private static final double MAX_ERROR = 0.5f; - // Maximum normalized frame duration before the frame is considered a pause - private static final double PAUSE_THRESHOLD = 15.0f; - private Instrumentation mInstrumentation; - private FrameStats stats; - private int numJankyFrames; - private long mLongestFrameNano = 0L; - - - /** - * Constructs a WindowAnimationFrameStatsMonitor instance. - */ - public WindowAnimationFrameStatsMonitor(Instrumentation instrumentation) { - mInstrumentation = instrumentation; - } - - private void analyze() { - int frameCount = stats.getFrameCount(); - long refreshPeriodNano = stats.getRefreshPeriodNano(); - - // Skip first frame - for (int i = 2; i < frameCount; i++) { - // Handle frames that have not been presented. - if (stats.getFramePresentedTimeNano(i) == UNDEFINED_TIME_NANO) { - // The animation must not have completed. Warn and break out of the loop. - Log.w(TAG, "Skipping fenced frame."); - break; - } - long frameDurationNano = stats.getFramePresentedTimeNano(i) - - stats.getFramePresentedTimeNano(i - 1); - double normalized = (double) frameDurationNano / refreshPeriodNano; - if (normalized < PAUSE_THRESHOLD) { - if (normalized > 1.0f + MAX_ERROR) { - numJankyFrames++; - } - mLongestFrameNano = Math.max(mLongestFrameNano, frameDurationNano); - } - } - } - - @Override - public void start() { - // Clear out any previous data - numJankyFrames = 0; - mLongestFrameNano = 0; - mInstrumentation.getUiAutomation().clearWindowAnimationFrameStats(); - } - - @Override - public void stop() { - stats = mInstrumentation.getUiAutomation().getWindowAnimationFrameStats(); - analyze(); - } - - public boolean jankyFramesDetected() { - return stats.getFrameCount() > 0 && numJankyFrames > 0; - } - - @Override - public String toString() { - return stats.toString() + - " RefreshPeriodNano:" + stats.getRefreshPeriodNano() + - " NumJankyFrames:" + numJankyFrames + - " LongestFrameNano:" + mLongestFrameNano; - } -}
\ No newline at end of file diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.java deleted file mode 100644 index ae160b68c976..000000000000 --- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker.monitor; - -import android.os.RemoteException; -import android.view.IWindowManager; -import android.view.WindowManagerGlobal; - -/** - * Captures WindowManager trace from WindowManager. - */ -public class WindowManagerTraceMonitor extends TraceMonitor { - private IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); - - public WindowManagerTraceMonitor() { - traceFileName = "wm_trace.pb"; - } - - @Override - public void start() { - try { - wm.startWindowTrace(); - } catch (RemoteException e) { - throw new RuntimeException("Could not start trace", e); - } - } - - @Override - public void stop() { - try { - wm.stopWindowTrace(); - } catch (RemoteException e) { - throw new RuntimeException("Could not stop trace", e); - } - } - - @Override - public boolean isEnabled() throws RemoteException{ - return wm.isWindowTraceEnabled(); - } -} diff --git a/tests/FlickerTests/lib/test/AndroidManifest.xml b/tests/FlickerTests/lib/test/AndroidManifest.xml deleted file mode 100644 index 6451a5710821..000000000000 --- a/tests/FlickerTests/lib/test/AndroidManifest.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * Copyright 2018 Google Inc. All Rights Reserved. - --> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.server.wm.flicker"> - - <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27"/> - <!-- Read and write traces from external storage --> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> - <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> - <!-- Capture screen contents --> - <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" /> - <!-- Run layers trace --> - <uses-permission android:name="android.permission.HARDWARE_TEST"/> - <application android:label="FlickerLibTest"> - <uses-library android:name="android.test.runner"/> - </application> - - <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="com.android.server.wm.flicker" - android:label="WindowManager Flicker Lib Test"> - </instrumentation> - -</manifest>
\ No newline at end of file diff --git a/tests/FlickerTests/lib/test/AndroidTest.xml b/tests/FlickerTests/lib/test/AndroidTest.xml deleted file mode 100644 index e4cc298a2aa8..000000000000 --- a/tests/FlickerTests/lib/test/AndroidTest.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * Copyright 2018 Google Inc. All Rights Reserved. - --> -<configuration description="Config for WindowManager Flicker Tests"> - <target_preparer class="com.google.android.tradefed.targetprep.GoogleDeviceSetup"> - <!-- keeps the screen on during tests --> - <option name="screen-always-on" value="on" /> - <!-- prevents the phone from restarting --> - <option name="force-skip-system-props" value="true" /> - </target_preparer> - <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> - <option name="cleanup-apks" value="true"/> - <option name="test-file-name" value="FlickerLibTest.apk"/> - </target_preparer> - <test class="com.android.tradefed.testtype.AndroidJUnitTest"> - <option name="package" value="com.android.server.wm.flicker"/> - <option name="hidden-api-checks" value="false" /> - </test> -</configuration> diff --git a/tests/FlickerTests/lib/test/assets/testdata/layers_trace_emptyregion.pb b/tests/FlickerTests/lib/test/assets/testdata/layers_trace_emptyregion.pb Binary files differdeleted file mode 100644 index 98ee6f3ed269..000000000000 --- a/tests/FlickerTests/lib/test/assets/testdata/layers_trace_emptyregion.pb +++ /dev/null diff --git a/tests/FlickerTests/lib/test/assets/testdata/layers_trace_invalid_layer_visibility.pb b/tests/FlickerTests/lib/test/assets/testdata/layers_trace_invalid_layer_visibility.pb Binary files differdeleted file mode 100644 index 20572d79d826..000000000000 --- a/tests/FlickerTests/lib/test/assets/testdata/layers_trace_invalid_layer_visibility.pb +++ /dev/null diff --git a/tests/FlickerTests/lib/test/assets/testdata/layers_trace_orphanlayers.pb b/tests/FlickerTests/lib/test/assets/testdata/layers_trace_orphanlayers.pb Binary files differdeleted file mode 100644 index af4079707c69..000000000000 --- a/tests/FlickerTests/lib/test/assets/testdata/layers_trace_orphanlayers.pb +++ /dev/null diff --git a/tests/FlickerTests/lib/test/assets/testdata/wm_trace_openchrome.pb b/tests/FlickerTests/lib/test/assets/testdata/wm_trace_openchrome.pb Binary files differdeleted file mode 100644 index b3f31706f55c..000000000000 --- a/tests/FlickerTests/lib/test/assets/testdata/wm_trace_openchrome.pb +++ /dev/null diff --git a/tests/FlickerTests/lib/test/assets/testdata/wm_trace_openchrome2.pb b/tests/FlickerTests/lib/test/assets/testdata/wm_trace_openchrome2.pb Binary files differdeleted file mode 100644 index b3b73ce0518a..000000000000 --- a/tests/FlickerTests/lib/test/assets/testdata/wm_trace_openchrome2.pb +++ /dev/null diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/AssertionsCheckerTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/AssertionsCheckerTest.java deleted file mode 100644 index 8e7fe1b4f942..000000000000 --- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/AssertionsCheckerTest.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker; - -import static com.google.common.truth.Truth.assertThat; - -import com.android.server.wm.flicker.Assertions.Result; - -import org.junit.Test; - -import java.util.ArrayList; -import java.util.List; - -/** - * Contains {@link AssertionsChecker} tests. - * To run this test: {@code atest FlickerLibTest:AssertionsCheckerTest} - */ -public class AssertionsCheckerTest { - - /** - * Returns a list of SimpleEntry objects with {@code data} and incremental timestamps starting - * at 0. - */ - private static List<SimpleEntry> getTestEntries(int... data) { - List<SimpleEntry> entries = new ArrayList<>(); - for (int i = 0; i < data.length; i++) { - entries.add(new SimpleEntry(i, data[i])); - } - return entries; - } - - @Test - public void canCheckAllEntries() { - AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>(); - checker.add(SimpleEntry::isData42, "isData42"); - - List<Result> failures = checker.test(getTestEntries(1, 1, 1, 1, 1)); - - assertThat(failures).hasSize(5); - } - - @Test - public void canCheckFirstEntry() { - AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>(); - checker.checkFirstEntry(); - checker.add(SimpleEntry::isData42, "isData42"); - - List<Result> failures = checker.test(getTestEntries(1, 1, 1, 1, 1)); - - assertThat(failures).hasSize(1); - assertThat(failures.get(0).timestamp).isEqualTo(0); - } - - @Test - public void canCheckLastEntry() { - AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>(); - checker.checkLastEntry(); - checker.add(SimpleEntry::isData42, "isData42"); - - List<Result> failures = checker.test(getTestEntries(1, 1, 1, 1, 1)); - - assertThat(failures).hasSize(1); - assertThat(failures.get(0).timestamp).isEqualTo(4); - } - - @Test - public void canCheckRangeOfEntries() { - AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>(); - checker.filterByRange(1, 2); - checker.add(SimpleEntry::isData42, "isData42"); - - List<Result> failures = checker.test(getTestEntries(1, 42, 42, 1, 1)); - - assertThat(failures).hasSize(0); - } - - @Test - public void emptyRangePasses() { - AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>(); - checker.filterByRange(9, 10); - checker.add(SimpleEntry::isData42, "isData42"); - - List<Result> failures = checker.test(getTestEntries(1, 1, 1, 1, 1)); - - assertThat(failures).isEmpty(); - } - - @Test - public void canCheckChangingAssertions() { - AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>(); - checker.add(SimpleEntry::isData42, "isData42"); - checker.add(SimpleEntry::isData0, "isData0"); - checker.checkChangingAssertions(); - - List<Result> failures = checker.test(getTestEntries(42, 0, 0, 0, 0)); - - assertThat(failures).isEmpty(); - } - - @Test - public void canCheckChangingAssertions_withNoAssertions() { - AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>(); - checker.checkChangingAssertions(); - - List<Result> failures = checker.test(getTestEntries(42, 0, 0, 0, 0)); - - assertThat(failures).isEmpty(); - } - - @Test - public void canCheckChangingAssertions_withSingleAssertion() { - AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>(); - checker.add(SimpleEntry::isData42, "isData42"); - checker.checkChangingAssertions(); - - List<Result> failures = checker.test(getTestEntries(42, 42, 42, 42, 42)); - - assertThat(failures).isEmpty(); - } - - @Test - public void canFailCheckChangingAssertions_ifStartingAssertionFails() { - AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>(); - checker.add(SimpleEntry::isData42, "isData42"); - checker.add(SimpleEntry::isData0, "isData0"); - checker.checkChangingAssertions(); - - List<Result> failures = checker.test(getTestEntries(0, 0, 0, 0, 0)); - - assertThat(failures).hasSize(1); - } - - @Test - public void canFailCheckChangingAssertions_ifStartingAssertionAlwaysPasses() { - AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>(); - checker.add(SimpleEntry::isData42, "isData42"); - checker.add(SimpleEntry::isData0, "isData0"); - checker.checkChangingAssertions(); - - List<Result> failures = checker.test(getTestEntries(0, 0, 0, 0, 0)); - - assertThat(failures).hasSize(1); - } - - static class SimpleEntry implements ITraceEntry { - long timestamp; - int data; - - SimpleEntry(long timestamp, int data) { - this.timestamp = timestamp; - this.data = data; - } - - @Override - public long getTimestamp() { - return timestamp; - } - - Result isData42() { - return new Result(this.data == 42, this.timestamp, "is42", ""); - } - - Result isData0() { - return new Result(this.data == 0, this.timestamp, "is42", ""); - } - } -} diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/AssertionsTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/AssertionsTest.java deleted file mode 100644 index 7fd178ca6e51..000000000000 --- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/AssertionsTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker; - -import static com.google.common.truth.Truth.assertThat; - -import com.android.server.wm.flicker.Assertions.Result; - -import org.junit.Test; - -/** - * Contains {@link Assertions} tests. - * To run this test: {@code atest FlickerLibTest:AssertionsTest} - */ -public class AssertionsTest { - @Test - public void traceEntryAssertionCanNegateResult() { - Assertions.TraceAssertion<Integer> assertNumEquals42 = - getIntegerTraceEntryAssertion(); - - assertThat(assertNumEquals42.apply(1).success).isFalse(); - assertThat(assertNumEquals42.negate().apply(1).success).isTrue(); - - assertThat(assertNumEquals42.apply(42).success).isTrue(); - assertThat(assertNumEquals42.negate().apply(42).success).isFalse(); - } - - @Test - public void resultCanBeNegated() { - String reason = "Everything is fine!"; - Result result = new Result(true, 0, "TestAssert", reason); - Result negatedResult = result.negate(); - assertThat(negatedResult.success).isFalse(); - assertThat(negatedResult.reason).isEqualTo(reason); - assertThat(negatedResult.assertionName).isEqualTo("!TestAssert"); - } - - private Assertions.TraceAssertion<Integer> getIntegerTraceEntryAssertion() { - return (num) -> { - if (num == 42) { - return new Result(true, "Num equals 42"); - } - return new Result(false, "Num doesn't equal 42, actual:" + num); - }; - } -}
\ No newline at end of file diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/LayersTraceSubjectTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/LayersTraceSubjectTest.java deleted file mode 100644 index d06c5d76552b..000000000000 --- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/LayersTraceSubjectTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker; - -import static com.android.server.wm.flicker.LayersTraceSubject.assertThat; -import static com.android.server.wm.flicker.TestFileUtils.readTestFile; - -import static com.google.common.truth.Truth.assertWithMessage; - -import static org.junit.Assert.fail; - -import android.graphics.Rect; - -import org.junit.Test; - -import java.nio.file.Paths; - -/** - * Contains {@link LayersTraceSubject} tests. - * To run this test: {@code atest FlickerLibTest:LayersTraceSubjectTest} - */ -public class LayersTraceSubjectTest { - private static final Rect displayRect = new Rect(0, 0, 1440, 2880); - - private static LayersTrace readLayerTraceFromFile(String relativePath) { - try { - return LayersTrace.parseFrom(readTestFile(relativePath), Paths.get(relativePath)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Test - public void testCanDetectEmptyRegionFromLayerTrace() { - LayersTrace layersTraceEntries = readLayerTraceFromFile("layers_trace_emptyregion.pb"); - try { - assertThat(layersTraceEntries).coversRegion(displayRect).forAllEntries(); - fail("Assertion passed"); - } catch (AssertionError e) { - assertWithMessage("Contains path to trace") - .that(e.getMessage()).contains("layers_trace_emptyregion.pb"); - assertWithMessage("Contains timestamp") - .that(e.getMessage()).contains("0h38m28s8ms"); - assertWithMessage("Contains assertion function") - .that(e.getMessage()).contains("coversRegion"); - assertWithMessage("Contains debug info") - .that(e.getMessage()).contains("Region to test: " + displayRect); - assertWithMessage("Contains debug info") - .that(e.getMessage()).contains("first empty point: 0, 99"); - } - } - - @Test - public void testCanDetectIncorrectVisibilityFromLayerTrace() { - LayersTrace layersTraceEntries = readLayerTraceFromFile( - "layers_trace_invalid_layer_visibility.pb"); - try { - assertThat(layersTraceEntries).showsLayer("com.android.server.wm.flicker.testapp") - .then().hidesLayer("com.android.server.wm.flicker.testapp").forAllEntries(); - fail("Assertion passed"); - } catch (AssertionError e) { - assertWithMessage("Contains path to trace") - .that(e.getMessage()).contains("layers_trace_invalid_layer_visibility.pb"); - assertWithMessage("Contains timestamp") - .that(e.getMessage()).contains("70h13m14s303ms"); - assertWithMessage("Contains assertion function") - .that(e.getMessage()).contains("!isVisible"); - assertWithMessage("Contains debug info") - .that(e.getMessage()).contains( - "com.android.server.wm.flicker.testapp/com.android.server.wm.flicker.testapp" - + ".SimpleActivity#0 is visible"); - } - } -} diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/LayersTraceTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/LayersTraceTest.java deleted file mode 100644 index 7d77126fd7d4..000000000000 --- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/LayersTraceTest.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker; - -import static com.android.server.wm.flicker.TestFileUtils.readTestFile; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; - -import static org.junit.Assert.fail; - -import android.content.Context; -import android.graphics.Point; -import android.graphics.Rect; -import android.view.WindowManager; - -import androidx.test.InstrumentationRegistry; - -import org.junit.Test; - -import java.util.List; -import java.util.stream.Collectors; - -/** - * Contains {@link LayersTrace} tests. - * To run this test: {@code atest FlickerLibTest:LayersTraceTest} - */ -public class LayersTraceTest { - private static LayersTrace readLayerTraceFromFile(String relativePath) { - try { - return LayersTrace.parseFrom(readTestFile(relativePath)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private static Rect getDisplayBounds() { - Point display = new Point(); - WindowManager wm = - (WindowManager) InstrumentationRegistry.getContext().getSystemService( - Context.WINDOW_SERVICE); - wm.getDefaultDisplay().getRealSize(display); - return new Rect(0, 0, display.x, display.y); - } - - @Test - public void canParseAllLayers() { - LayersTrace trace = readLayerTraceFromFile( - "layers_trace_emptyregion.pb"); - assertThat(trace.getEntries()).isNotEmpty(); - assertThat(trace.getEntries().get(0).getTimestamp()).isEqualTo(2307984557311L); - assertThat(trace.getEntries().get(trace.getEntries().size() - 1).getTimestamp()) - .isEqualTo(2308521813510L); - List<LayersTrace.Layer> flattenedLayers = trace.getEntries().get(0).asFlattenedLayers(); - String msg = "Layers:\n" + flattenedLayers.stream().map(layer -> layer.mProto.name) - .collect(Collectors.joining("\n\t")); - assertWithMessage(msg).that(flattenedLayers).hasSize(47); - } - - @Test - public void canParseVisibleLayers() { - LayersTrace trace = readLayerTraceFromFile( - "layers_trace_emptyregion.pb"); - assertThat(trace.getEntries()).isNotEmpty(); - assertThat(trace.getEntries().get(0).getTimestamp()).isEqualTo(2307984557311L); - assertThat(trace.getEntries().get(trace.getEntries().size() - 1).getTimestamp()) - .isEqualTo(2308521813510L); - List<LayersTrace.Layer> flattenedLayers = trace.getEntries().get(0).asFlattenedLayers(); - List<LayersTrace.Layer> visibleLayers = flattenedLayers.stream() - .filter(layer -> layer.isVisible() && !layer.isHiddenByParent()) - .collect(Collectors.toList()); - - String msg = "Visible Layers:\n" + visibleLayers.stream() - .map(layer -> layer.mProto.name) - .collect(Collectors.joining("\n\t")); - - assertWithMessage(msg).that(visibleLayers).hasSize(9); - } - - @Test - public void canParseLayerHierarchy() { - LayersTrace trace = readLayerTraceFromFile( - "layers_trace_emptyregion.pb"); - assertThat(trace.getEntries()).isNotEmpty(); - assertThat(trace.getEntries().get(0).getTimestamp()).isEqualTo(2307984557311L); - assertThat(trace.getEntries().get(trace.getEntries().size() - 1).getTimestamp()) - .isEqualTo(2308521813510L); - List<LayersTrace.Layer> layers = trace.getEntries().get(0).getRootLayers(); - assertThat(layers).hasSize(2); - assertThat(layers.get(0).mChildren).hasSize(layers.get(0).mProto.children.length); - assertThat(layers.get(1).mChildren).hasSize(layers.get(1).mProto.children.length); - } - - // b/76099859 - @Test - public void canDetectOrphanLayers() { - try { - readLayerTraceFromFile( - "layers_trace_orphanlayers.pb"); - fail("Failed to detect orphaned layers."); - } catch (RuntimeException exception) { - assertThat(exception.getMessage()).contains( - "Failed to parse layers trace. Found orphan layers " - + "with parent layer id:1006 : 49"); - } - } - - // b/75276931 - @Test - public void canDetectUncoveredRegion() { - LayersTrace trace = readLayerTraceFromFile( - "layers_trace_emptyregion.pb"); - LayersTrace.Entry entry = trace.getEntry(2308008331271L); - - Assertions.Result result = entry.coversRegion(getDisplayBounds()); - - assertThat(result.failed()).isTrue(); - assertThat(result.reason).contains("Region to test: Rect(0, 0 - 1440, 2880)"); - assertThat(result.reason).contains("first empty point: 0, 99"); - assertThat(result.reason).contains("visible regions:"); - assertWithMessage("Reason contains list of visible regions") - .that(result.reason).contains("StatusBar#0Rect(0, 0 - 1440, 98"); - } - - // Visible region tests - @Test - public void canTestLayerVisibleRegion_layerDoesNotExist() { - LayersTrace trace = readLayerTraceFromFile( - "layers_trace_emptyregion.pb"); - LayersTrace.Entry entry = trace.getEntry(2308008331271L); - - final Rect expectedVisibleRegion = new Rect(0, 0, 1, 1); - Assertions.Result result = entry.hasVisibleRegion("ImaginaryLayer", - expectedVisibleRegion); - - assertThat(result.failed()).isTrue(); - assertThat(result.reason).contains("Could not find ImaginaryLayer"); - } - - @Test - public void canTestLayerVisibleRegion_layerDoesNotHaveExpectedVisibleRegion() { - LayersTrace trace = readLayerTraceFromFile( - "layers_trace_emptyregion.pb"); - LayersTrace.Entry entry = trace.getEntry(2307993020072L); - - final Rect expectedVisibleRegion = new Rect(0, 0, 1, 1); - Assertions.Result result = entry.hasVisibleRegion("NexusLauncherActivity#2", - expectedVisibleRegion); - - assertThat(result.failed()).isTrue(); - assertThat(result.reason).contains( - "Layer com.google.android.apps.nexuslauncher/com.google.android.apps" - + ".nexuslauncher.NexusLauncherActivity#2 is invisible: activeBuffer=null" - + " type != ColorLayer flags=1 (FLAG_HIDDEN set) visible region is empty"); - } - - @Test - public void canTestLayerVisibleRegion_layerIsHiddenByParent() { - LayersTrace trace = readLayerTraceFromFile( - "layers_trace_emptyregion.pb"); - LayersTrace.Entry entry = trace.getEntry(2308455948035L); - - final Rect expectedVisibleRegion = new Rect(0, 0, 1, 1); - Assertions.Result result = entry.hasVisibleRegion( - "SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main", - expectedVisibleRegion); - - assertThat(result.failed()).isTrue(); - assertThat(result.reason).contains( - "Layer SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main#0 is " - + "hidden by parent: com.android.chrome/com.google.android.apps.chrome" - + ".Main#0"); - } - - @Test - public void canTestLayerVisibleRegion_incorrectRegionSize() { - LayersTrace trace = readLayerTraceFromFile( - "layers_trace_emptyregion.pb"); - LayersTrace.Entry entry = trace.getEntry(2308008331271L); - - final Rect expectedVisibleRegion = new Rect(0, 0, 1440, 99); - Assertions.Result result = entry.hasVisibleRegion( - "StatusBar", - expectedVisibleRegion); - - assertThat(result.failed()).isTrue(); - assertThat(result.reason).contains("StatusBar#0 has visible " - + "region:Rect(0, 0 - 1440, 98) expected:Rect(0, 0 - 1440, 99)"); - } - - @Test - public void canTestLayerVisibleRegion() { - LayersTrace trace = readLayerTraceFromFile( - "layers_trace_emptyregion.pb"); - LayersTrace.Entry entry = trace.getEntry(2308008331271L); - - final Rect expectedVisibleRegion = new Rect(0, 0, 1440, 98); - Assertions.Result result = entry.hasVisibleRegion("StatusBar", expectedVisibleRegion); - - assertThat(result.passed()).isTrue(); - } - - @Test - public void canTestLayerVisibleRegion_layerIsNotVisible() { - LayersTrace trace = readLayerTraceFromFile( - "layers_trace_invalid_layer_visibility.pb"); - LayersTrace.Entry entry = trace.getEntry(252794268378458L); - - Assertions.Result result = entry.isVisible("com.android.server.wm.flicker.testapp"); - assertThat(result.failed()).isTrue(); - assertThat(result.reason).contains( - "Layer com.android.server.wm.flicker.testapp/com.android.server.wm.flicker" - + ".testapp.SimpleActivity#0 is invisible: type != ColorLayer visible " - + "region is empty"); - } -} diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/TransitionRunnerTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/TransitionRunnerTest.java deleted file mode 100644 index 9c5e2059a0e6..000000000000 --- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/TransitionRunnerTest.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -import android.os.Environment; - -import com.android.server.wm.flicker.TransitionRunner.TransitionBuilder; -import com.android.server.wm.flicker.TransitionRunner.TransitionResult; -import com.android.server.wm.flicker.monitor.LayersTraceMonitor; -import com.android.server.wm.flicker.monitor.ScreenRecorder; -import com.android.server.wm.flicker.monitor.WindowAnimationFrameStatsMonitor; -import com.android.server.wm.flicker.monitor.WindowManagerTraceMonitor; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.InOrder; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.io.IOException; -import java.nio.file.Paths; -import java.util.List; - -/** - * Contains {@link TransitionRunner} tests. - * {@code atest FlickerLibTest:TransitionRunnerTest} - */ -public class TransitionRunnerTest { - @Mock - private SimpleUiTransitions mTransitionsMock; - @Mock - private ScreenRecorder mScreenRecorderMock; - @Mock - private WindowManagerTraceMonitor mWindowManagerTraceMonitorMock; - @Mock - private LayersTraceMonitor mLayersTraceMonitorMock; - @Mock - private WindowAnimationFrameStatsMonitor mWindowAnimationFrameStatsMonitor; - @InjectMocks - private TransitionBuilder mTransitionBuilder; - - @Before - public void init() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void transitionsRunInOrder() { - TransitionRunner.newBuilder() - .runBeforeAll(mTransitionsMock::turnOnDevice) - .runBefore(mTransitionsMock::openApp) - .run(mTransitionsMock::performMagic) - .runAfter(mTransitionsMock::closeApp) - .runAfterAll(mTransitionsMock::cleanUpTracks) - .skipLayersTrace() - .skipWindowManagerTrace() - .build() - .run(); - - InOrder orderVerifier = inOrder(mTransitionsMock); - orderVerifier.verify(mTransitionsMock).turnOnDevice(); - orderVerifier.verify(mTransitionsMock).openApp(); - orderVerifier.verify(mTransitionsMock).performMagic(); - orderVerifier.verify(mTransitionsMock).closeApp(); - orderVerifier.verify(mTransitionsMock).cleanUpTracks(); - } - - @Test - public void canCombineTransitions() { - TransitionRunner.newBuilder() - .runBeforeAll(mTransitionsMock::turnOnDevice) - .runBeforeAll(mTransitionsMock::turnOnDevice) - .runBefore(mTransitionsMock::openApp) - .runBefore(mTransitionsMock::openApp) - .run(mTransitionsMock::performMagic) - .run(mTransitionsMock::performMagic) - .runAfter(mTransitionsMock::closeApp) - .runAfter(mTransitionsMock::closeApp) - .runAfterAll(mTransitionsMock::cleanUpTracks) - .runAfterAll(mTransitionsMock::cleanUpTracks) - .skipLayersTrace() - .skipWindowManagerTrace() - .build() - .run(); - - final int wantedNumberOfInvocations = 2; - verify(mTransitionsMock, times(wantedNumberOfInvocations)).turnOnDevice(); - verify(mTransitionsMock, times(wantedNumberOfInvocations)).openApp(); - verify(mTransitionsMock, times(wantedNumberOfInvocations)).performMagic(); - verify(mTransitionsMock, times(wantedNumberOfInvocations)).closeApp(); - verify(mTransitionsMock, times(wantedNumberOfInvocations)).cleanUpTracks(); - } - - @Test - public void emptyTransitionPasses() { - List<TransitionResult> results = TransitionRunner.newBuilder() - .skipLayersTrace() - .skipWindowManagerTrace() - .build() - .run() - .getResults(); - assertThat(results).hasSize(1); - assertThat(results.get(0).layersTraceExists()).isFalse(); - assertThat(results.get(0).windowManagerTraceExists()).isFalse(); - assertThat(results.get(0).screenCaptureVideoExists()).isFalse(); - } - - @Test - public void canRepeatTransitions() { - final int wantedNumberOfInvocations = 10; - TransitionRunner.newBuilder() - .runBeforeAll(mTransitionsMock::turnOnDevice) - .runBefore(mTransitionsMock::openApp) - .run(mTransitionsMock::performMagic) - .runAfter(mTransitionsMock::closeApp) - .runAfterAll(mTransitionsMock::cleanUpTracks) - .repeat(wantedNumberOfInvocations) - .skipLayersTrace() - .skipWindowManagerTrace() - .build() - .run(); - verify(mTransitionsMock).turnOnDevice(); - verify(mTransitionsMock, times(wantedNumberOfInvocations)).openApp(); - verify(mTransitionsMock, times(wantedNumberOfInvocations)).performMagic(); - verify(mTransitionsMock, times(wantedNumberOfInvocations)).closeApp(); - verify(mTransitionsMock).cleanUpTracks(); - } - - private void emptyTask() { - - } - - @Test - public void canCaptureWindowManagerTrace() { - mTransitionBuilder - .run(this::emptyTask) - .includeJankyRuns() - .skipLayersTrace() - .withTag("mCaptureWmTraceTransitionRunner") - .build().run(); - InOrder orderVerifier = inOrder(mWindowManagerTraceMonitorMock); - orderVerifier.verify(mWindowManagerTraceMonitorMock).start(); - orderVerifier.verify(mWindowManagerTraceMonitorMock).stop(); - orderVerifier.verify(mWindowManagerTraceMonitorMock) - .save("mCaptureWmTraceTransitionRunner", 0); - verifyNoMoreInteractions(mWindowManagerTraceMonitorMock); - } - - @Test - public void canCaptureLayersTrace() { - mTransitionBuilder - .run(this::emptyTask) - .includeJankyRuns() - .skipWindowManagerTrace() - .withTag("mCaptureLayersTraceTransitionRunner") - .build().run(); - InOrder orderVerifier = inOrder(mLayersTraceMonitorMock); - orderVerifier.verify(mLayersTraceMonitorMock).start(); - orderVerifier.verify(mLayersTraceMonitorMock).stop(); - orderVerifier.verify(mLayersTraceMonitorMock) - .save("mCaptureLayersTraceTransitionRunner", 0); - verifyNoMoreInteractions(mLayersTraceMonitorMock); - } - - @Test - public void canRecordEachRun() throws IOException { - mTransitionBuilder - .run(this::emptyTask) - .withTag("mRecordEachRun") - .recordEachRun() - .includeJankyRuns() - .skipLayersTrace() - .skipWindowManagerTrace() - .repeat(2) - .build().run(); - InOrder orderVerifier = inOrder(mScreenRecorderMock); - orderVerifier.verify(mScreenRecorderMock).start(); - orderVerifier.verify(mScreenRecorderMock).stop(); - orderVerifier.verify(mScreenRecorderMock).save("mRecordEachRun", 0); - orderVerifier.verify(mScreenRecorderMock).start(); - orderVerifier.verify(mScreenRecorderMock).stop(); - orderVerifier.verify(mScreenRecorderMock).save("mRecordEachRun", 1); - verifyNoMoreInteractions(mScreenRecorderMock); - } - - @Test - public void canRecordAllRuns() throws IOException { - doReturn(Paths.get(Environment.getExternalStorageDirectory().getAbsolutePath(), - "mRecordAllRuns.mp4")).when(mScreenRecorderMock).save("mRecordAllRuns"); - mTransitionBuilder - .run(this::emptyTask) - .recordAllRuns() - .includeJankyRuns() - .skipLayersTrace() - .skipWindowManagerTrace() - .withTag("mRecordAllRuns") - .repeat(2) - .build().run(); - InOrder orderVerifier = inOrder(mScreenRecorderMock); - orderVerifier.verify(mScreenRecorderMock).start(); - orderVerifier.verify(mScreenRecorderMock).stop(); - orderVerifier.verify(mScreenRecorderMock).save("mRecordAllRuns"); - verifyNoMoreInteractions(mScreenRecorderMock); - } - - @Test - public void canSkipJankyRuns() { - doReturn(false).doReturn(true).doReturn(false) - .when(mWindowAnimationFrameStatsMonitor).jankyFramesDetected(); - List<TransitionResult> results = mTransitionBuilder - .run(this::emptyTask) - .skipLayersTrace() - .skipWindowManagerTrace() - .repeat(3) - .build().run().getResults(); - assertThat(results).hasSize(2); - } - - public static class SimpleUiTransitions { - public void turnOnDevice() { - } - - public void openApp() { - } - - public void performMagic() { - } - - public void closeApp() { - } - - public void cleanUpTracks() { - } - } -} diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/WindowManagerTraceTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/WindowManagerTraceTest.java deleted file mode 100644 index 49278718932c..000000000000 --- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/WindowManagerTraceTest.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker; - -import static com.android.server.wm.flicker.TestFileUtils.readTestFile; - -import static com.google.common.truth.Truth.assertThat; - -import com.android.server.wm.flicker.Assertions.Result; - -import org.junit.Before; -import org.junit.Test; - -/** - * Contains {@link WindowManagerTrace} tests. - * To run this test: {@code atest FlickerLibTest:WindowManagerTraceTest} - */ -public class WindowManagerTraceTest { - private WindowManagerTrace mTrace; - - private static WindowManagerTrace readWindowManagerTraceFromFile(String relativePath) { - try { - return WindowManagerTrace.parseFrom(readTestFile(relativePath)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Before - public void setup() { - mTrace = readWindowManagerTraceFromFile("wm_trace_openchrome.pb"); - } - - @Test - public void canParseAllEntries() { - assertThat(mTrace.getEntries().get(0).getTimestamp()).isEqualTo(241777211939236L); - assertThat(mTrace.getEntries().get(mTrace.getEntries().size() - 1).getTimestamp()).isEqualTo - (241779809471942L); - } - - @Test - public void canDetectAboveAppWindowVisibility() { - WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L); - Result result = entry.isAboveAppWindowVisible("NavigationBar"); - assertThat(result.passed()).isTrue(); - } - - @Test - public void canDetectBelowAppWindowVisibility() { - WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L); - Result result = entry.isBelowAppWindowVisible("wallpaper"); - assertThat(result.passed()).isTrue(); - } - - @Test - public void canDetectAppWindowVisibility() { - WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L); - Result result = entry.isAppWindowVisible("com.google.android.apps.nexuslauncher"); - assertThat(result.passed()).isTrue(); - } - - @Test - public void canFailWithReasonForVisibilityChecks_windowNotFound() { - WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L); - Result result = entry.isAboveAppWindowVisible("ImaginaryWindow"); - assertThat(result.failed()).isTrue(); - assertThat(result.reason).contains("ImaginaryWindow cannot be found"); - } - - @Test - public void canFailWithReasonForVisibilityChecks_windowNotVisible() { - WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L); - Result result = entry.isAboveAppWindowVisible("AssistPreviewPanel"); - assertThat(result.failed()).isTrue(); - assertThat(result.reason).contains("AssistPreviewPanel is invisible"); - } - - @Test - public void canDetectAppZOrder() { - WindowManagerTrace.Entry entry = mTrace.getEntry(241778130296410L); - Result result = entry.isVisibleAppWindowOnTop("com.google.android.apps.chrome"); - assertThat(result.passed()).isTrue(); - } - - @Test - public void canFailWithReasonForZOrderChecks_windowNotOnTop() { - WindowManagerTrace.Entry entry = mTrace.getEntry(241778130296410L); - Result result = entry.isVisibleAppWindowOnTop("com.google.android.apps.nexuslauncher"); - assertThat(result.failed()).isTrue(); - assertThat(result.reason).contains("wanted=com.google.android.apps.nexuslauncher"); - assertThat(result.reason).contains("found=com.android.chrome/" - + "com.google.android.apps.chrome.Main"); - } -} diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/WmTraceSubjectTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/WmTraceSubjectTest.java deleted file mode 100644 index d547a188a663..000000000000 --- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/WmTraceSubjectTest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker; - -import static com.android.server.wm.flicker.TestFileUtils.readTestFile; -import static com.android.server.wm.flicker.WmTraceSubject.assertThat; - -import org.junit.Test; - -/** - * Contains {@link WmTraceSubject} tests. - * To run this test: {@code atest FlickerLibTest:WmTraceSubjectTest} - */ -public class WmTraceSubjectTest { - private static WindowManagerTrace readWmTraceFromFile(String relativePath) { - try { - return WindowManagerTrace.parseFrom(readTestFile(relativePath)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Test - public void testCanTransitionInAppWindow() { - WindowManagerTrace trace = readWmTraceFromFile("wm_trace_openchrome2.pb"); - - assertThat(trace).showsAppWindowOnTop("com.google.android.apps.nexuslauncher/" - + ".NexusLauncherActivity").forRange(174684850717208L, 174685957511016L); - assertThat(trace).showsAppWindowOnTop( - "com.google.android.apps.nexuslauncher/.NexusLauncherActivity") - .then() - .showsAppWindowOnTop("com.android.chrome") - .forAllEntries(); - } -} diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/LayersTraceMonitorTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/LayersTraceMonitorTest.java deleted file mode 100644 index dbd6761a05b0..000000000000 --- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/LayersTraceMonitorTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker.monitor; - -import static android.surfaceflinger.nano.Layerstrace.LayersTraceFileProto.MAGIC_NUMBER_H; -import static android.surfaceflinger.nano.Layerstrace.LayersTraceFileProto.MAGIC_NUMBER_L; - -import static com.google.common.truth.Truth.assertThat; - -import android.surfaceflinger.nano.Layerstrace.LayersTraceFileProto; - -import com.google.common.io.Files; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.io.File; - -/** - * Contains {@link LayersTraceMonitor} tests. - * To run this test: {@code atest FlickerLibTest:LayersTraceMonitorTest} - */ -public class LayersTraceMonitorTest { - private LayersTraceMonitor mLayersTraceMonitor; - - @Before - public void setup() { - mLayersTraceMonitor = new LayersTraceMonitor(); - } - - @After - public void teardown() { - mLayersTraceMonitor.stop(); - mLayersTraceMonitor.getOutputTraceFilePath("captureLayersTrace").toFile().delete(); - } - - @Test - public void canStartLayersTrace() throws Exception { - mLayersTraceMonitor.start(); - assertThat(mLayersTraceMonitor.isEnabled()).isTrue(); - } - - @Test - public void canStopLayersTrace() throws Exception { - mLayersTraceMonitor.start(); - assertThat(mLayersTraceMonitor.isEnabled()).isTrue(); - mLayersTraceMonitor.stop(); - assertThat(mLayersTraceMonitor.isEnabled()).isFalse(); - } - - @Test - public void captureLayersTrace() throws Exception { - mLayersTraceMonitor.start(); - mLayersTraceMonitor.stop(); - File testFile = mLayersTraceMonitor.save("captureLayersTrace").toFile(); - assertThat(testFile.exists()).isTrue(); - byte[] trace = Files.toByteArray(testFile); - assertThat(trace.length).isGreaterThan(0); - LayersTraceFileProto mLayerTraceFileProto = LayersTraceFileProto.parseFrom(trace); - assertThat(mLayerTraceFileProto.magicNumber).isEqualTo( - (long) MAGIC_NUMBER_H << 32 | MAGIC_NUMBER_L); - } -} diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/ScreenRecorderTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/ScreenRecorderTest.java deleted file mode 100644 index e73eecc348f0..000000000000 --- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/ScreenRecorderTest.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker.monitor; - -import static android.os.SystemClock.sleep; - -import static com.android.server.wm.flicker.monitor.ScreenRecorder.DEFAULT_OUTPUT_PATH; -import static com.android.server.wm.flicker.monitor.ScreenRecorder.getPath; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.io.File; -import java.io.IOException; - -/** - * Contains {@link ScreenRecorder} tests. - * To run this test: {@code atest FlickerLibTest:ScreenRecorderTest} - */ -public class ScreenRecorderTest { - private static final String TEST_VIDEO_FILENAME = "test.mp4"; - private ScreenRecorder mScreenRecorder; - - @Before - public void setup() { - mScreenRecorder = new ScreenRecorder(); - } - - @After - public void teardown() { - DEFAULT_OUTPUT_PATH.toFile().delete(); - getPath(TEST_VIDEO_FILENAME).toFile().delete(); - } - - @Test - public void videoIsRecorded() { - mScreenRecorder.start(); - sleep(100); - mScreenRecorder.stop(); - File file = DEFAULT_OUTPUT_PATH.toFile(); - assertThat(file.exists()).isTrue(); - } - - @Test - public void videoCanBeSaved() { - mScreenRecorder.start(); - sleep(100); - mScreenRecorder.stop(); - mScreenRecorder.save(TEST_VIDEO_FILENAME); - File file = getPath(TEST_VIDEO_FILENAME).toFile(); - assertThat(file.exists()).isTrue(); - } -} diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitorTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitorTest.java deleted file mode 100644 index dd6fed04d3e6..000000000000 --- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitorTest.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker.monitor; - -import static com.android.server.wm.flicker.AutomationUtils.wakeUpAndGoToHomeScreen; - -import androidx.test.InstrumentationRegistry; - -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; - -/** - * Contains {@link WindowAnimationFrameStatsMonitor} tests. - * To run this test: {@code atest FlickerLibTest:WindowAnimationFrameStatsMonitorTest} - */ -public class WindowAnimationFrameStatsMonitorTest { - private WindowAnimationFrameStatsMonitor mWindowAnimationFrameStatsMonitor; - - @Before - public void setup() { - mWindowAnimationFrameStatsMonitor = new WindowAnimationFrameStatsMonitor( - InstrumentationRegistry.getInstrumentation()); - wakeUpAndGoToHomeScreen(); - } - - // TODO(vishnun) - @Ignore("Disabled until app-helper libraries are available.") - @Test - public void captureWindowAnimationFrameStats() throws Exception { - mWindowAnimationFrameStatsMonitor.start(); - //AppHelperWrapper.getInstance().getHelper(CHROME).open(); - //AppHelperWrapper.getInstance().getHelper(CHROME).exit(); - mWindowAnimationFrameStatsMonitor.stop(); - } -} diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitorTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitorTest.java deleted file mode 100644 index 56284d7d516a..000000000000 --- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitorTest.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker.monitor; - -import static com.android.server.wm.nano.WindowManagerTraceFileProto.MAGIC_NUMBER_H; -import static com.android.server.wm.nano.WindowManagerTraceFileProto.MAGIC_NUMBER_L; - -import static com.google.common.truth.Truth.assertThat; - -import com.android.server.wm.nano.WindowManagerTraceFileProto; - -import com.google.common.io.Files; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.io.File; - -/** - * Contains {@link WindowManagerTraceMonitor} tests. - * To run this test: {@code atest FlickerLibTest:WindowManagerTraceMonitorTest} - */ -public class WindowManagerTraceMonitorTest { - private WindowManagerTraceMonitor mWindowManagerTraceMonitor; - - @Before - public void setup() { - mWindowManagerTraceMonitor = new WindowManagerTraceMonitor(); - } - - @After - public void teardown() { - mWindowManagerTraceMonitor.stop(); - mWindowManagerTraceMonitor.getOutputTraceFilePath("captureWindowTrace").toFile().delete(); - } - - @Test - public void canStartWindowTrace() throws Exception { - mWindowManagerTraceMonitor.start(); - assertThat(mWindowManagerTraceMonitor.isEnabled()).isTrue(); - } - - @Test - public void canStopWindowTrace() throws Exception { - mWindowManagerTraceMonitor.start(); - assertThat(mWindowManagerTraceMonitor.isEnabled()).isTrue(); - mWindowManagerTraceMonitor.stop(); - assertThat(mWindowManagerTraceMonitor.isEnabled()).isFalse(); - } - - @Test - public void captureWindowTrace() throws Exception { - mWindowManagerTraceMonitor.start(); - mWindowManagerTraceMonitor.stop(); - File testFile = mWindowManagerTraceMonitor.save("captureWindowTrace").toFile(); - assertThat(testFile.exists()).isTrue(); - byte[] trace = Files.toByteArray(testFile); - assertThat(trace.length).isGreaterThan(0); - WindowManagerTraceFileProto mWindowTraceFileProto = WindowManagerTraceFileProto.parseFrom( - trace); - assertThat(mWindowTraceFileProto.magicNumber).isEqualTo( - (long) MAGIC_NUMBER_H << 32 | MAGIC_NUMBER_L); - } -} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ChangeAppRotationTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/ChangeAppRotationTest.java index b6860cbd8d96..5a66e805c575 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ChangeAppRotationTest.java +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ChangeAppRotationTest.java @@ -29,11 +29,15 @@ import android.util.Log; import android.view.Surface; import androidx.test.InstrumentationRegistry; +import androidx.test.filters.FlakyTest; import androidx.test.filters.LargeTest; import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @@ -44,24 +48,25 @@ import java.util.Collection; * Cycle through supported app rotations. * To run this test: {@code atest FlickerTest:ChangeAppRotationTest} */ -@RunWith(Parameterized.class) @LargeTest +@RunWith(Parameterized.class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) public class ChangeAppRotationTest extends FlickerTestBase { - private int beginRotation; - private int endRotation; + private int mBeginRotation; + private int mEndRotation; public ChangeAppRotationTest(String beginRotationName, String endRotationName, int beginRotation, int endRotation) { - this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), + this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), "com.android.server.wm.flicker.testapp", "SimpleApp"); - this.beginRotation = beginRotation; - this.endRotation = endRotation; + this.mBeginRotation = beginRotation; + this.mEndRotation = endRotation; } @Parameters(name = "{0}-{1}") public static Collection<Object[]> getParams() { int[] supportedRotations = - {Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270}; + {Surface.ROTATION_0, Surface.ROTATION_90}; Collection<Object[]> params = new ArrayList<>(); for (int begin : supportedRotations) { for (int end : supportedRotations) { @@ -77,15 +82,20 @@ public class ChangeAppRotationTest extends FlickerTestBase { @Before public void runTransition() { super.runTransition( - changeAppRotation(testApp, uiDevice, beginRotation, endRotation).build()); + changeAppRotation(mTestApp, mUiDevice, mBeginRotation, mEndRotation) + .includeJankyRuns().build()); } + @FlakyTest(bugId = 140855415) + @Ignore("Waiting bug feedback") @Test public void checkVisibility_navBarWindowIsAlwaysVisible() { checkResults(result -> assertThat(result) .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries()); } + @FlakyTest(bugId = 140855415) + @Ignore("Waiting bug feedback") @Test public void checkVisibility_statusBarWindowIsAlwaysVisible() { checkResults(result -> assertThat(result) @@ -94,8 +104,8 @@ public class ChangeAppRotationTest extends FlickerTestBase { @Test public void checkPosition_navBarLayerRotatesAndScales() { - Rect startingPos = getNavigationBarPosition(beginRotation); - Rect endingPos = getNavigationBarPosition(endRotation); + Rect startingPos = getNavigationBarPosition(mBeginRotation); + Rect endingPos = getNavigationBarPosition(mEndRotation); checkResults(result -> { LayersTraceSubject.assertThat(result) .hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos) @@ -108,22 +118,22 @@ public class ChangeAppRotationTest extends FlickerTestBase { @Test public void checkPosition_appLayerRotates() { - Rect startingPos = getAppPosition(beginRotation); - Rect endingPos = getAppPosition(endRotation); + Rect startingPos = getAppPosition(mBeginRotation); + Rect endingPos = getAppPosition(mEndRotation); Log.e(TAG, "startingPos=" + startingPos + " endingPos=" + endingPos); checkResults(result -> { LayersTraceSubject.assertThat(result) - .hasVisibleRegion(testApp.getPackage(), startingPos).inTheBeginning(); + .hasVisibleRegion(mTestApp.getPackage(), startingPos).inTheBeginning(); LayersTraceSubject.assertThat(result) - .hasVisibleRegion(testApp.getPackage(), endingPos).atTheEnd(); + .hasVisibleRegion(mTestApp.getPackage(), endingPos).atTheEnd(); } ); } @Test public void checkPosition_statusBarLayerScales() { - Rect startingPos = getStatusBarPosition(beginRotation); - Rect endingPos = getStatusBarPosition(endRotation); + Rect startingPos = getStatusBarPosition(mBeginRotation); + Rect endingPos = getStatusBarPosition(mEndRotation); checkResults(result -> { LayersTraceSubject.assertThat(result) .hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, startingPos) @@ -134,12 +144,16 @@ public class ChangeAppRotationTest extends FlickerTestBase { ); } + @FlakyTest(bugId = 140855415) + @Ignore("Waiting bug feedback") @Test public void checkVisibility_navBarLayerIsAlwaysVisible() { checkResults(result -> LayersTraceSubject.assertThat(result) .showsLayer(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries()); } + @FlakyTest(bugId = 140855415) + @Ignore("Waiting bug feedback") @Test public void checkVisibility_statusBarLayerIsAlwaysVisible() { checkResults(result -> LayersTraceSubject.assertThat(result) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeAutoOpenWindowToAppTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeAutoOpenWindowToAppTest.java new file mode 100644 index 000000000000..022f798e82f5 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeAutoOpenWindowToAppTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker; + +import static com.android.server.wm.flicker.CommonTransitions.editTextLoseFocusToApp; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.FlakyTest; +import androidx.test.filters.LargeTest; + +import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper; + +import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.junit.runners.Parameterized; + +/** + * Test IME window closing back to app window transitions. + * To run this test: {@code atest FlickerTests:CloseImeWindowToAppTest} + */ +@LargeTest +@RunWith(Parameterized.class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class CloseImeAutoOpenWindowToAppTest extends CloseImeWindowToAppTest { + + public CloseImeAutoOpenWindowToAppTest(String beginRotationName, int beginRotation) { + super(beginRotationName, beginRotation); + + mTestApp = new ImeAppAutoFocusHelper(InstrumentationRegistry.getInstrumentation()); + } + + @Before + public void runTransition() { + run(editTextLoseFocusToApp((ImeAppAutoFocusHelper) mTestApp, mUiDevice, mBeginRotation) + .includeJankyRuns().build()); + } + + @FlakyTest(bugId = 141458352) + @Ignore("Waiting bug feedback") + @Test + public void checkVisibility_imeLayerBecomesInvisible() { + super.checkVisibility_imeLayerBecomesInvisible(); + } + + @FlakyTest(bugId = 141458352) + @Ignore("Waiting bug feedback") + @Test + public void checkVisibility_imeAppLayerIsAlwaysVisible() { + super.checkVisibility_imeAppLayerIsAlwaysVisible(); + } + + @FlakyTest(bugId = 141458352) + @Ignore("Waiting bug feedback") + @Test + public void checkVisibility_imeAppWindowIsAlwaysVisible() { + super.checkVisibility_imeAppWindowIsAlwaysVisible(); + } + +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeAutoOpenWindowToHomeTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeAutoOpenWindowToHomeTest.java new file mode 100644 index 000000000000..d6f994b5c0d5 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeAutoOpenWindowToHomeTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker; + +import static com.android.server.wm.flicker.CommonTransitions.editTextLoseFocusToHome; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.FlakyTest; +import androidx.test.filters.LargeTest; + +import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper; + +import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.junit.runners.Parameterized; + +/** + * Test IME window closing back to app window transitions. + * To run this test: {@code atest FlickerTests:CloseImeWindowToAppTest} + */ +@LargeTest +@RunWith(Parameterized.class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class CloseImeAutoOpenWindowToHomeTest extends CloseImeWindowToHomeTest { + + public CloseImeAutoOpenWindowToHomeTest(String beginRotationName, int beginRotation) { + super(beginRotationName, beginRotation); + + mTestApp = new ImeAppAutoFocusHelper(InstrumentationRegistry.getInstrumentation()); + } + + @Before + public void runTransition() { + run(editTextLoseFocusToHome((ImeAppAutoFocusHelper) mTestApp, mUiDevice, mBeginRotation) + .includeJankyRuns().build()); + } + + @FlakyTest(bugId = 141458352) + @Ignore("Waiting bug feedback") + @Test + public void checkVisibility_imeWindowBecomesInvisible() { + super.checkVisibility_imeWindowBecomesInvisible(); + } + + @FlakyTest(bugId = 141458352) + @Ignore("Waiting bug feedback") + @Test + public void checkVisibility_imeLayerBecomesInvisible() { + super.checkVisibility_imeLayerBecomesInvisible(); + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToAppTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToAppTest.java index 6590b86f1499..28da3af2b7c5 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToAppTest.java +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToAppTest.java @@ -17,34 +17,39 @@ package com.android.server.wm.flicker; import static com.android.server.wm.flicker.CommonTransitions.editTextLoseFocusToApp; -import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds; - -import android.platform.helpers.IAppHelper; import androidx.test.InstrumentationRegistry; import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; + +import com.android.server.wm.flicker.helpers.ImeAppHelper; import org.junit.Before; +import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.junit.runners.Parameterized; /** * Test IME window closing back to app window transitions. * To run this test: {@code atest FlickerTests:CloseImeWindowToAppTest} */ @LargeTest -@RunWith(AndroidJUnit4.class) -public class CloseImeWindowToAppTest extends FlickerTestBase { +@RunWith(Parameterized.class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class CloseImeWindowToAppTest extends NonRotationTestBase { + + static final String IME_WINDOW_TITLE = "InputMethod"; + + public CloseImeWindowToAppTest(String beginRotationName, int beginRotation) { + super(beginRotationName, beginRotation); - private static final String IME_WINDOW_TITLE = "InputMethod"; - private IAppHelper mImeTestApp = new StandardAppHelper( - InstrumentationRegistry.getInstrumentation(), - "com.android.server.wm.flicker.testapp", "ImeApp"); + mTestApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation()); + } @Before public void runTransition() { - super.runTransition(editTextLoseFocusToApp(uiDevice) + run(editTextLoseFocusToApp((ImeAppHelper) mTestApp, mUiDevice, mBeginRotation) .includeJankyRuns().build()); } @@ -60,20 +65,14 @@ public class CloseImeWindowToAppTest extends FlickerTestBase { @Test public void checkVisibility_imeAppLayerIsAlwaysVisible() { checkResults(result -> LayersTraceSubject.assertThat(result) - .showsLayer(mImeTestApp.getPackage()) + .showsLayer(mTestApp.getPackage()) .forAllEntries()); } @Test public void checkVisibility_imeAppWindowIsAlwaysVisible() { checkResults(result -> WmTraceSubject.assertThat(result) - .showsAppWindowOnTop(mImeTestApp.getPackage()) + .showsAppWindowOnTop(mTestApp.getPackage()) .forAllEntries()); } - - @Test - public void checkCoveredRegion_noUncoveredRegions() { - checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion( - getDisplayBounds()).forAllEntries()); - } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java index 4771b02000c0..f740af9b89bf 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java @@ -17,34 +17,40 @@ package com.android.server.wm.flicker; import static com.android.server.wm.flicker.CommonTransitions.editTextLoseFocusToHome; -import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds; - -import android.platform.helpers.IAppHelper; import androidx.test.InstrumentationRegistry; import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; + +import com.android.server.wm.flicker.helpers.ImeAppHelper; import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.junit.runners.Parameterized; /** * Test IME window closing to home transitions. * To run this test: {@code atest FlickerTests:CloseImeWindowToHomeTest} */ @LargeTest -@RunWith(AndroidJUnit4.class) -public class CloseImeWindowToHomeTest extends FlickerTestBase { +@RunWith(Parameterized.class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class CloseImeWindowToHomeTest extends NonRotationTestBase { + + static final String IME_WINDOW_TITLE = "InputMethod"; + + public CloseImeWindowToHomeTest(String beginRotationName, int beginRotation) { + super(beginRotationName, beginRotation); - private static final String IME_WINDOW_TITLE = "InputMethod"; - private IAppHelper mImeTestApp = new StandardAppHelper( - InstrumentationRegistry.getInstrumentation(), - "com.android.server.wm.flicker.testapp", "ImeApp"); + mTestApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation()); + } @Before public void runTransition() { - super.runTransition(editTextLoseFocusToHome(uiDevice) + run(editTextLoseFocusToHome((ImeAppHelper) mTestApp, mUiDevice, mBeginRotation) .includeJankyRuns().build()); } @@ -57,6 +63,7 @@ public class CloseImeWindowToHomeTest extends FlickerTestBase { .forAllEntries()); } + @Ignore("Flaky") @Test public void checkVisibility_imeLayerBecomesInvisible() { checkResults(result -> LayersTraceSubject.assertThat(result) @@ -66,27 +73,22 @@ public class CloseImeWindowToHomeTest extends FlickerTestBase { .forAllEntries()); } + @Ignore("Flaky") @Test public void checkVisibility_imeAppLayerBecomesInvisible() { checkResults(result -> LayersTraceSubject.assertThat(result) - .showsLayer(mImeTestApp.getPackage()) + .showsLayer(mTestApp.getPackage()) .then() - .hidesLayer(mImeTestApp.getPackage()) + .hidesLayer(mTestApp.getPackage()) .forAllEntries()); } @Test public void checkVisibility_imeAppWindowBecomesInvisible() { checkResults(result -> WmTraceSubject.assertThat(result) - .showsAppWindowOnTop(mImeTestApp.getPackage()) + .showsAppWindowOnTop(mTestApp.getPackage()) .then() - .hidesAppWindowOnTop(mImeTestApp.getPackage()) + .hidesAppWindowOnTop(mTestApp.getPackage()) .forAllEntries()); } - - @Test - public void checkCoveredRegion_noUncoveredRegions() { - checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion( - getDisplayBounds()).forAllEntries()); - } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.java index 65888acc184b..e033d0ab9578 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.java +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.java @@ -19,12 +19,12 @@ package com.android.server.wm.flicker; import static android.os.SystemClock.sleep; import static android.view.Surface.rotationToString; -import static com.android.server.wm.flicker.AutomationUtils.clearRecents; -import static com.android.server.wm.flicker.AutomationUtils.closePipWindow; -import static com.android.server.wm.flicker.AutomationUtils.exitSplitScreen; -import static com.android.server.wm.flicker.AutomationUtils.expandPipWindow; -import static com.android.server.wm.flicker.AutomationUtils.launchSplitScreen; -import static com.android.server.wm.flicker.AutomationUtils.stopPackage; +import static com.android.server.wm.flicker.helpers.AutomationUtils.clearRecents; +import static com.android.server.wm.flicker.helpers.AutomationUtils.closePipWindow; +import static com.android.server.wm.flicker.helpers.AutomationUtils.exitSplitScreen; +import static com.android.server.wm.flicker.helpers.AutomationUtils.expandPipWindow; +import static com.android.server.wm.flicker.helpers.AutomationUtils.launchSplitScreen; +import static com.android.server.wm.flicker.helpers.AutomationUtils.stopPackage; import android.content.Context; import android.content.Intent; @@ -37,9 +37,10 @@ import android.support.test.uiautomator.Until; import android.util.Rational; import android.view.Surface; -import androidx.test.InstrumentationRegistry; - import com.android.server.wm.flicker.TransitionRunner.TransitionBuilder; +import com.android.server.wm.flicker.helpers.AutomationUtils; +import com.android.server.wm.flicker.helpers.ImeAppHelper; +import com.android.server.wm.flicker.helpers.PipAppHelper; /** * Collection of common transitions which can be used to test different apps or scenarios. @@ -66,32 +67,24 @@ class CommonTransitions { device.setOrientationNatural(); } // Wait for animation to complete - sleep(3000); + sleep(1000); } catch (RemoteException e) { throw new RuntimeException(e); } } - private static void clickEditTextWidget(UiDevice device, IAppHelper testApp) { - UiObject2 editText = device.findObject(By.res(testApp.getPackage(), "plain_text_input")); - editText.click(); - sleep(500); - } - - private static void clickEnterPipButton(UiDevice device, IAppHelper testApp) { - UiObject2 enterPipButton = device.findObject(By.res(testApp.getPackage(), "enter_pip")); - enterPipButton.click(); - sleep(500); - } - static TransitionBuilder openAppWarm(IAppHelper testApp, UiDevice - device) { + device, int beginRotation) { return TransitionRunner.newBuilder() - .withTag("OpenAppWarm_" + testApp.getLauncherName()) + .withTag("OpenAppWarm_" + testApp.getLauncherName() + + rotationToString(beginRotation)) + .recordAllRuns() .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) + .runBeforeAll(() -> setRotation(device, beginRotation)) .runBeforeAll(testApp::open) .runBefore(device::pressHome) .runBefore(device::waitForIdle) + .runBefore(() -> setRotation(device, beginRotation)) .run(testApp::open) .runAfterAll(testApp::exit) .runAfterAll(AutomationUtils::setDefaultWait) @@ -102,6 +95,7 @@ class CommonTransitions { device) { return TransitionRunner.newBuilder() .withTag("closeAppWithBackKey_" + testApp.getLauncherName()) + .recordAllRuns() .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) .runBefore(testApp::open) .runBefore(device::waitForIdle) @@ -116,6 +110,7 @@ class CommonTransitions { device) { return TransitionRunner.newBuilder() .withTag("closeAppWithHomeKey_" + testApp.getLauncherName()) + .recordAllRuns() .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) .runBefore(testApp::open) .runBefore(device::waitForIdle) @@ -126,16 +121,20 @@ class CommonTransitions { .repeat(ITERATIONS); } - static TransitionBuilder getOpenAppCold(IAppHelper testApp, - UiDevice device) { + static TransitionBuilder openAppCold(IAppHelper testApp, + UiDevice device, int beginRotation) { return TransitionRunner.newBuilder() - .withTag("OpenAppCold_" + testApp.getLauncherName()) + .withTag("OpenAppCold_" + testApp.getLauncherName() + + rotationToString(beginRotation)) + .recordAllRuns() .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) .runBefore(device::pressHome) + .runBeforeAll(() -> setRotation(device, beginRotation)) .runBefore(testApp::exit) .runBefore(device::waitForIdle) .run(testApp::open) .runAfterAll(testApp::exit) + .runAfterAll(() -> setRotation(device, Surface.ROTATION_0)) .repeat(ITERATIONS); } @@ -145,6 +144,7 @@ class CommonTransitions { .withTag("changeAppRotation_" + testApp.getLauncherName() + rotationToString(beginRotation) + "_" + rotationToString(endRotation)) + .recordAllRuns() .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) .runBeforeAll(testApp::open) .runBefore(() -> setRotation(device, beginRotation)) @@ -161,6 +161,7 @@ class CommonTransitions { rotationToString(beginRotation) + "_" + rotationToString(endRotation); return TransitionRunner.newBuilder() .withTag(testTag) + .recordAllRuns() .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) .runBeforeAll(() -> { context.startActivity(intent); @@ -178,6 +179,7 @@ class CommonTransitions { static TransitionBuilder appToSplitScreen(IAppHelper testApp, UiDevice device) { return TransitionRunner.newBuilder() .withTag("appToSplitScreen_" + testApp.getLauncherName()) + .recordAllRuns() .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) .runBefore(testApp::open) .runBefore(device::waitForIdle) @@ -191,6 +193,7 @@ class CommonTransitions { static TransitionBuilder splitScreenToLauncher(IAppHelper testApp, UiDevice device) { return TransitionRunner.newBuilder() .withTag("splitScreenToLauncher_" + testApp.getLauncherName()) + .recordAllRuns() .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) .runBefore(testApp::open) .runBefore(device::waitForIdle) @@ -200,28 +203,33 @@ class CommonTransitions { .repeat(ITERATIONS); } - static TransitionBuilder editTextSetFocus(UiDevice device) { - IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), - "com.android.server.wm.flicker.testapp", "ImeApp"); + static TransitionBuilder editTextSetFocus(ImeAppHelper testApp, UiDevice device, + int beginRotation) { return TransitionRunner.newBuilder() - .withTag("editTextSetFocus_" + testApp.getLauncherName()) + .withTag("editTextSetFocus_" + testApp.getLauncherName() + + rotationToString(beginRotation)) + .recordAllRuns() .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) .runBefore(device::pressHome) + .runBefore(() -> setRotation(device, beginRotation)) .runBefore(testApp::open) - .run(() -> clickEditTextWidget(device, testApp)) + .run(() -> testApp.clickEditTextWidget(device)) .runAfterAll(testApp::exit) .repeat(ITERATIONS); } - static TransitionBuilder resizeSplitScreen(IAppHelper testAppTop, IAppHelper testAppBottom, - UiDevice device, Rational startRatio, Rational stopRatio) { - String testTag = "resizeSplitScreen_" + testAppTop.getLauncherName() + "_" + - testAppBottom.getLauncherName() + "_" + - startRatio.toString().replace("/", ":") + "_to_" + - stopRatio.toString().replace("/", ":"); + static TransitionBuilder resizeSplitScreen(IAppHelper testAppTop, ImeAppHelper testAppBottom, + UiDevice device, int beginRotation, Rational startRatio, Rational stopRatio) { + String testTag = "resizeSplitScreen_" + testAppTop.getLauncherName() + "_" + + testAppBottom.getLauncherName() + "_" + + startRatio.toString().replace("/", ":") + "_to_" + + stopRatio.toString().replace("/", ":") + "_" + + rotationToString(beginRotation); return TransitionRunner.newBuilder() .withTag(testTag) + .recordAllRuns() .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) + .runBeforeAll(() -> setRotation(device, beginRotation)) .runBeforeAll(() -> clearRecents(device)) .runBefore(testAppBottom::open) .runBefore(device::pressHome) @@ -230,9 +238,10 @@ class CommonTransitions { .runBefore(() -> launchSplitScreen(device)) .runBefore(() -> { UiObject2 snapshot = device.findObject( - By.res("com.google.android.apps.nexuslauncher", "snapshot")); + By.res(device.getLauncherPackageName(), "snapshot")); snapshot.click(); }) + .runBefore(() -> testAppBottom.clickEditTextWidget(device)) .runBefore(() -> AutomationUtils.resizeSplitScreen(device, startRatio)) .run(() -> AutomationUtils.resizeSplitScreen(device, stopRatio)) .runAfter(() -> exitSplitScreen(device)) @@ -242,77 +251,75 @@ class CommonTransitions { .repeat(ITERATIONS); } - static TransitionBuilder editTextLoseFocusToHome(UiDevice device) { - IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), - "com.android.server.wm.flicker.testapp", "ImeApp"); + static TransitionBuilder editTextLoseFocusToHome(ImeAppHelper testApp, UiDevice device, + int beginRotation) { return TransitionRunner.newBuilder() - .withTag("editTextLoseFocusToHome_" + testApp.getLauncherName()) + .withTag("editTextLoseFocusToHome_" + testApp.getLauncherName() + + rotationToString(beginRotation)) + .recordAllRuns() .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) .runBefore(device::pressHome) + .runBefore(() -> setRotation(device, beginRotation)) .runBefore(testApp::open) - .runBefore(() -> clickEditTextWidget(device, testApp)) + .runBefore(() -> testApp.clickEditTextWidget(device)) .run(device::pressHome) .run(device::waitForIdle) .runAfterAll(testApp::exit) .repeat(ITERATIONS); } - static TransitionBuilder editTextLoseFocusToApp(UiDevice device) { - IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), - "com.android.server.wm.flicker.testapp", "ImeApp"); + static TransitionBuilder editTextLoseFocusToApp(ImeAppHelper testApp, UiDevice device, + int beginRotation) { return TransitionRunner.newBuilder() - .withTag("editTextLoseFocusToApp_" + testApp.getLauncherName()) + .withTag("editTextLoseFocusToApp_" + testApp.getLauncherName() + + rotationToString(beginRotation)) + .recordAllRuns() .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) .runBefore(device::pressHome) + .runBefore(() -> setRotation(device, beginRotation)) .runBefore(testApp::open) - .runBefore(() -> clickEditTextWidget(device, testApp)) + .runBefore(() -> testApp.clickEditTextWidget(device)) .run(device::pressBack) .run(device::waitForIdle) .runAfterAll(testApp::exit) .repeat(ITERATIONS); } - static TransitionBuilder enterPipMode(UiDevice device) { - IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), - "com.android.server.wm.flicker.testapp", "PipApp"); + static TransitionBuilder enterPipMode(PipAppHelper testApp, UiDevice device) { return TransitionRunner.newBuilder() .withTag("enterPipMode_" + testApp.getLauncherName()) .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) .runBefore(device::pressHome) .runBefore(testApp::open) - .run(() -> clickEnterPipButton(device, testApp)) + .run(() -> testApp.clickEnterPipButton(device)) .runAfter(() -> closePipWindow(device)) .runAfterAll(testApp::exit) .repeat(ITERATIONS); } - static TransitionBuilder exitPipModeToHome(UiDevice device) { - IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), - "com.android.server.wm.flicker.testapp", "PipApp"); + static TransitionBuilder exitPipModeToHome(PipAppHelper testApp, UiDevice device) { return TransitionRunner.newBuilder() .withTag("exitPipModeToHome_" + testApp.getLauncherName()) .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) .runBefore(device::pressHome) .runBefore(testApp::open) - .runBefore(() -> clickEnterPipButton(device, testApp)) + .runBefore(() -> testApp.clickEnterPipButton(device)) .run(() -> closePipWindow(device)) .run(device::waitForIdle) .runAfterAll(testApp::exit) .repeat(ITERATIONS); } - static TransitionBuilder exitPipModeToApp(UiDevice device) { - IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), - "com.android.server.wm.flicker.testapp", "PipApp"); + static TransitionBuilder exitPipModeToApp(PipAppHelper testApp, UiDevice device) { return TransitionRunner.newBuilder() .withTag("exitPipModeToApp_" + testApp.getLauncherName()) .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) .runBefore(device::pressHome) .runBefore(testApp::open) - .runBefore(() -> clickEnterPipButton(device, testApp)) + .runBefore(() -> testApp.clickEnterPipButton(device)) .run(() -> expandPipWindow(device)) .run(device::waitForIdle) .runAfterAll(testApp::exit) .repeat(ITERATIONS); } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.java index 61cca0d6b53f..8f0177c7afc5 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.java +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.java @@ -22,40 +22,50 @@ import android.util.Rational; import android.view.Surface; import androidx.test.InstrumentationRegistry; +import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; +import com.android.server.wm.flicker.helpers.ImeAppHelper; +import com.android.server.wm.flicker.helpers.PipAppHelper; + +import org.junit.FixMethodOrder; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; /** * Tests to help debug individual transitions, capture video recordings and create test cases. */ +@LargeTest @Ignore("Used for debugging transitions used in FlickerTests.") @RunWith(AndroidJUnit4.class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) public class DebugTest { private IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), "com.android.server.wm.flicker.testapp", "SimpleApp"); private UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); /** - * atest FlickerTest:DebugTests#openAppCold + * atest FlickerTests:DebugTest#openAppCold */ @Test public void openAppCold() { - CommonTransitions.getOpenAppCold(testApp, uiDevice).recordAllRuns().build().run(); + CommonTransitions.openAppCold(testApp, uiDevice, Surface.ROTATION_0) + .recordAllRuns().build().run(); } /** - * atest FlickerTest:DebugTests#openAppWarm + * atest FlickerTests:DebugTest#openAppWarm */ @Test public void openAppWarm() { - CommonTransitions.openAppWarm(testApp, uiDevice).recordAllRuns().build().run(); + CommonTransitions.openAppWarm(testApp, uiDevice, Surface.ROTATION_0) + .recordAllRuns().build().run(); } /** - * atest FlickerTest:DebugTests#changeOrientationFromNaturalToLeft + * atest FlickerTests:DebugTest#changeOrientationFromNaturalToLeft */ @Test public void changeOrientationFromNaturalToLeft() { @@ -64,7 +74,7 @@ public class DebugTest { } /** - * atest FlickerTest:DebugTests#closeAppWithBackKey + * atest FlickerTests:DebugTest#closeAppWithBackKey */ @Test public void closeAppWithBackKey() { @@ -72,7 +82,7 @@ public class DebugTest { } /** - * atest FlickerTest:DebugTests#closeAppWithHomeKey + * atest FlickerTests:DebugTest#closeAppWithHomeKey */ @Test public void closeAppWithHomeKey() { @@ -80,7 +90,7 @@ public class DebugTest { } /** - * atest FlickerTest:DebugTests#openAppToSplitScreen + * atest FlickerTests:DebugTest#openAppToSplitScreen */ @Test public void openAppToSplitScreen() { @@ -89,7 +99,7 @@ public class DebugTest { } /** - * atest FlickerTest:DebugTests#splitScreenToLauncher + * atest FlickerTests:DebugTest#splitScreenToLauncher */ @Test public void splitScreenToLauncher() { @@ -99,70 +109,80 @@ public class DebugTest { } /** - * atest FlickerTest:DebugTests#resizeSplitScreen + * atest FlickerTests:DebugTest#resizeSplitScreen */ @Test public void resizeSplitScreen() { - IAppHelper bottomApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), - "com.android.server.wm.flicker.testapp", "ImeApp"); - CommonTransitions.resizeSplitScreen(testApp, bottomApp, uiDevice, new Rational(1, 3), - new Rational(2, 3)).includeJankyRuns().recordEachRun().build().run(); + ImeAppHelper bottomApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation()); + CommonTransitions.resizeSplitScreen(testApp, bottomApp, uiDevice, Surface.ROTATION_0, + new Rational(1, 3), new Rational(2, 3)) + .includeJankyRuns().recordEachRun().build().run(); } // IME tests /** - * atest FlickerTest:DebugTests#editTextSetFocus + * atest FlickerTests:DebugTest#editTextSetFocus */ @Test public void editTextSetFocus() { - CommonTransitions.editTextSetFocus(uiDevice).includeJankyRuns().recordEachRun() + ImeAppHelper testApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation()); + CommonTransitions.editTextSetFocus(testApp, uiDevice, Surface.ROTATION_0) + .includeJankyRuns().recordEachRun() .build().run(); } /** - * atest FlickerTest:DebugTests#editTextLoseFocusToHome + * atest FlickerTests:DebugTest#editTextLoseFocusToHome */ @Test public void editTextLoseFocusToHome() { - CommonTransitions.editTextLoseFocusToHome(uiDevice).includeJankyRuns().recordEachRun() + ImeAppHelper testApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation()); + CommonTransitions.editTextLoseFocusToHome(testApp, uiDevice, Surface.ROTATION_0) + .includeJankyRuns().recordEachRun() .build().run(); } /** - * atest FlickerTest:DebugTests#editTextLoseFocusToApp + * atest FlickerTests:DebugTest#editTextLoseFocusToApp */ @Test public void editTextLoseFocusToApp() { - CommonTransitions.editTextLoseFocusToHome(uiDevice).includeJankyRuns().recordEachRun() + ImeAppHelper testApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation()); + CommonTransitions.editTextLoseFocusToHome(testApp, uiDevice, Surface.ROTATION_0) + .includeJankyRuns().recordEachRun() .build().run(); } // PIP tests /** - * atest FlickerTest:DebugTests#enterPipMode + * atest FlickerTests:DebugTest#enterPipMode */ @Test public void enterPipMode() { - CommonTransitions.enterPipMode(uiDevice).includeJankyRuns().recordEachRun().build().run(); + PipAppHelper testApp = new PipAppHelper(InstrumentationRegistry.getInstrumentation()); + CommonTransitions.enterPipMode(testApp, uiDevice).includeJankyRuns().recordEachRun() + .build().run(); } /** - * atest FlickerTest:DebugTests#exitPipModeToHome + * atest FlickerTests:DebugTest#exitPipModeToHome */ @Test public void exitPipModeToHome() { - CommonTransitions.exitPipModeToHome(uiDevice).includeJankyRuns().recordEachRun() + PipAppHelper testApp = new PipAppHelper(InstrumentationRegistry.getInstrumentation()); + CommonTransitions.exitPipModeToHome(testApp, uiDevice).includeJankyRuns().recordEachRun() .build().run(); } /** - * atest FlickerTest:DebugTests#exitPipModeToApp + * atest FlickerTests:DebugTest#exitPipModeToApp */ @Test public void exitPipModeToApp() { - CommonTransitions.exitPipModeToApp(uiDevice).includeJankyRuns().recordEachRun() + PipAppHelper testApp = new PipAppHelper(InstrumentationRegistry.getInstrumentation()); + CommonTransitions.exitPipModeToApp(testApp, uiDevice).includeJankyRuns().recordEachRun() .build().run(); } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java b/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java index 00e11c0cef41..883d59ea8a92 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java @@ -16,20 +16,23 @@ package com.android.server.wm.flicker; -import static com.android.server.wm.flicker.AutomationUtils.setDefaultWait; +import static androidx.test.InstrumentationRegistry.getInstrumentation; + +import static com.android.server.wm.flicker.helpers.AutomationUtils.setDefaultWait; import static com.google.common.truth.Truth.assertWithMessage; +import android.os.Bundle; import android.platform.helpers.IAppHelper; +import android.support.test.InstrumentationRegistry; import android.support.test.uiautomator.UiDevice; import android.util.Log; -import androidx.test.InstrumentationRegistry; - import com.android.server.wm.flicker.TransitionRunner.TransitionResult; import org.junit.After; import org.junit.AfterClass; +import org.junit.Before; import java.util.HashMap; import java.util.List; @@ -51,10 +54,16 @@ public class FlickerTestBase { static final String DOCKED_STACK_DIVIDER = "DockedStackDivider"; private static HashMap<String, List<TransitionResult>> transitionResults = new HashMap<>(); - IAppHelper testApp; - UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); - private List<TransitionResult> results; - private TransitionResult lastResult = null; + IAppHelper mTestApp; + UiDevice mUiDevice; + private List<TransitionResult> mResults; + private TransitionResult mLastResult = null; + + @Before + public void setUp() { + InstrumentationRegistry.registerInstance(getInstrumentation(), new Bundle()); + mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + } /** * Teardown any system settings and clean up test artifacts from the file system. @@ -89,16 +98,23 @@ public class FlickerTestBase { /** * Runs a transition, returns a cached result if the transition has run before. */ - void runTransition(TransitionRunner transition) { + void run(TransitionRunner transition) { if (transitionResults.containsKey(transition.getTestTag())) { - results = transitionResults.get(transition.getTestTag()); + mResults = transitionResults.get(transition.getTestTag()); return; } - results = transition.run().getResults(); + mResults = transition.run().getResults(); /* Fail if we don't have any results due to jank */ assertWithMessage("No results to test because all transition runs were invalid because " - + "of Jank").that(results).isNotEmpty(); - transitionResults.put(transition.getTestTag(), results); + + "of Jank").that(mResults).isNotEmpty(); + transitionResults.put(transition.getTestTag(), mResults); + } + + /** + * Runs a transition, returns a cached result if the transition has run before. + */ + void runTransition(TransitionRunner transition) { + run(transition); } /** @@ -106,11 +122,11 @@ public class FlickerTestBase { */ void checkResults(Consumer<TransitionResult> assertion) { - for (TransitionResult result : results) { - lastResult = result; + for (TransitionResult result : mResults) { + mLastResult = result; assertion.accept(result); } - lastResult = null; + mLastResult = null; } /** @@ -119,8 +135,8 @@ public class FlickerTestBase { */ @After public void markArtifactsForSaving() { - if (lastResult != null) { - lastResult.flagForSaving(); + if (mLastResult != null) { + mLastResult.flagForSaving(); } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/NonRotationTestBase.java b/tests/FlickerTests/src/com/android/server/wm/flicker/NonRotationTestBase.java new file mode 100644 index 000000000000..54941dc0f585 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/NonRotationTestBase.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker; + +import static android.view.Surface.rotationToString; + +import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds; + +import android.graphics.Rect; +import android.view.Surface; + +import androidx.test.filters.FlakyTest; + +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runners.Parameterized.Parameters; + +import java.util.ArrayList; +import java.util.Collection; + +public abstract class NonRotationTestBase extends FlickerTestBase { + + int mBeginRotation; + + public NonRotationTestBase(String beginRotationName, int beginRotation) { + this.mBeginRotation = beginRotation; + } + + @Parameters(name = "{0}") + public static Collection<Object[]> getParams() { + int[] supportedRotations = + {Surface.ROTATION_0, Surface.ROTATION_90}; + Collection<Object[]> params = new ArrayList<>(); + + for (int begin : supportedRotations) { + params.add(new Object[]{rotationToString(begin), begin}); + } + + return params; + } + + @FlakyTest(bugId = 141361128) + @Ignore("Waiting bug feedback") + @Test + public void checkCoveredRegion_noUncoveredRegions() { + Rect displayBounds = getDisplayBounds(mBeginRotation); + checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion( + displayBounds).forAllEntries()); + } + + @FlakyTest(bugId = 141361128) + @Ignore("Waiting bug feedback") + @Test + public void checkVisibility_navBarLayerIsAlwaysVisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .showsLayer(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries()); + } + + @FlakyTest(bugId = 141361128) + @Ignore("Waiting bug feedback") + @Test + public void checkVisibility_statusBarLayerIsAlwaysVisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .showsLayer(STATUS_BAR_WINDOW_TITLE).forAllEntries()); + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppColdTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppColdTest.java index 7818c4e4ba50..efdfaee60e64 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppColdTest.java +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppColdTest.java @@ -16,91 +16,70 @@ package com.android.server.wm.flicker; -import static com.android.server.wm.flicker.CommonTransitions.getOpenAppCold; -import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds; +import static com.android.server.wm.flicker.CommonTransitions.openAppCold; import static com.android.server.wm.flicker.WmTraceSubject.assertThat; import androidx.test.InstrumentationRegistry; +import androidx.test.filters.FlakyTest; import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.junit.runners.Parameterized; /** * Test cold launch app from launcher. * To run this test: {@code atest FlickerTests:OpenAppColdTest} */ @LargeTest -@RunWith(AndroidJUnit4.class) -public class OpenAppColdTest extends FlickerTestBase { +@RunWith(Parameterized.class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class OpenAppColdTest extends NonRotationTestBase { - public OpenAppColdTest() { - this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), + public OpenAppColdTest(String beginRotationName, int beginRotation) { + super(beginRotationName, beginRotation); + + this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), "com.android.server.wm.flicker.testapp", "SimpleApp"); } @Before public void runTransition() { - super.runTransition(getOpenAppCold(testApp, uiDevice).build()); - } - - @Test - public void checkVisibility_navBarWindowIsAlwaysVisible() { - checkResults(result -> assertThat(result) - .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries()); - } - - @Test - public void checkVisibility_statusBarWindowIsAlwaysVisible() { - checkResults(result -> assertThat(result) - .showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE).forAllEntries()); + run(openAppCold(mTestApp, mUiDevice, mBeginRotation) + .includeJankyRuns().build()); } @Test public void checkVisibility_wallpaperWindowBecomesInvisible() { checkResults(result -> assertThat(result) - .showsBelowAppWindow("wallpaper") + .showsBelowAppWindow("Wallpaper") .then() - .hidesBelowAppWindow("wallpaper") + .hidesBelowAppWindow("Wallpaper") .forAllEntries()); } + @FlakyTest(bugId = 140855415) + @Ignore("Waiting bug feedback") @Test public void checkZOrder_appWindowReplacesLauncherAsTopWindow() { checkResults(result -> assertThat(result) .showsAppWindowOnTop( - "com.google.android.apps.nexuslauncher/.NexusLauncherActivity") + "com.android.launcher3/.Launcher") .then() - .showsAppWindowOnTop(testApp.getPackage()) + .showsAppWindowOnTop(mTestApp.getPackage()) .forAllEntries()); } @Test - public void checkCoveredRegion_noUncoveredRegions() { - checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion( - getDisplayBounds()).forAllEntries()); - } - - @Test - public void checkVisibility_navBarLayerIsAlwaysVisible() { - checkResults(result -> LayersTraceSubject.assertThat(result) - .showsLayer(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries()); - } - - @Test - public void checkVisibility_statusBarLayerIsAlwaysVisible() { - checkResults(result -> LayersTraceSubject.assertThat(result) - .showsLayer(STATUS_BAR_WINDOW_TITLE).forAllEntries()); - } - - @Test public void checkVisibility_wallpaperLayerBecomesInvisible() { checkResults(result -> LayersTraceSubject.assertThat(result) - .showsLayer("wallpaper") + .showsLayer("Wallpaper") .then() - .hidesLayer("wallpaper") + .hidesLayer("Wallpaper") .forAllEntries()); } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppToSplitScreenTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppToSplitScreenTest.java index 63018ec1d9e7..f8b7938901a8 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppToSplitScreenTest.java +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppToSplitScreenTest.java @@ -24,8 +24,10 @@ import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Before; +import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; /** * Test open app to split screen. @@ -33,16 +35,17 @@ import org.junit.runner.RunWith; */ @LargeTest @RunWith(AndroidJUnit4.class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) public class OpenAppToSplitScreenTest extends FlickerTestBase { public OpenAppToSplitScreenTest() { - this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), + this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), "com.android.server.wm.flicker.testapp", "SimpleApp"); } @Before public void runTransition() { - super.runTransition(appToSplitScreen(testApp, uiDevice).includeJankyRuns().build()); + super.runTransition(appToSplitScreen(mTestApp, mUiDevice).includeJankyRuns().build()); } @Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppWarmTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppWarmTest.java index 1aba93056c89..7ce6315f529a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppWarmTest.java +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppWarmTest.java @@ -17,90 +17,69 @@ package com.android.server.wm.flicker; import static com.android.server.wm.flicker.CommonTransitions.openAppWarm; -import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds; import static com.android.server.wm.flicker.WmTraceSubject.assertThat; import androidx.test.InstrumentationRegistry; +import androidx.test.filters.FlakyTest; import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.junit.runners.Parameterized; /** * Test warm launch app. * To run this test: {@code atest FlickerTests:OpenAppWarmTest} */ @LargeTest -@RunWith(AndroidJUnit4.class) -public class OpenAppWarmTest extends FlickerTestBase { +@RunWith(Parameterized.class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class OpenAppWarmTest extends NonRotationTestBase { - public OpenAppWarmTest() { - this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), + public OpenAppWarmTest(String beginRotationName, int beginRotation) { + super(beginRotationName, beginRotation); + + this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), "com.android.server.wm.flicker.testapp", "SimpleApp"); } @Before public void runTransition() { - super.runTransition(openAppWarm(testApp, uiDevice).build()); - } - - @Test - public void checkVisibility_navBarIsAlwaysVisible() { - checkResults(result -> assertThat(result) - .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries()); - } - - @Test - public void checkVisibility_statusBarIsAlwaysVisible() { - checkResults(result -> assertThat(result) - .showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE).forAllEntries()); + super.runTransition(openAppWarm(mTestApp, mUiDevice, mBeginRotation) + .includeJankyRuns().build()); } @Test public void checkVisibility_wallpaperBecomesInvisible() { checkResults(result -> assertThat(result) - .showsBelowAppWindow("wallpaper") + .showsBelowAppWindow("Wallpaper") .then() - .hidesBelowAppWindow("wallpaper") + .hidesBelowAppWindow("Wallpaper") .forAllEntries()); } + @FlakyTest(bugId = 140855415) + @Ignore("Waiting bug feedback") @Test public void checkZOrder_appWindowReplacesLauncherAsTopWindow() { checkResults(result -> assertThat(result) .showsAppWindowOnTop( - "com.google.android.apps.nexuslauncher/.NexusLauncherActivity") + "com.android.launcher3/.Launcher") .then() - .showsAppWindowOnTop(testApp.getPackage()) + .showsAppWindowOnTop(mTestApp.getPackage()) .forAllEntries()); } @Test - public void checkCoveredRegion_noUncoveredRegions() { - checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion( - getDisplayBounds()).forAllEntries()); - } - - @Test - public void checkVisibility_navBarLayerIsAlwaysVisible() { - checkResults(result -> LayersTraceSubject.assertThat(result) - .showsLayer(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries()); - } - - @Test - public void checkVisibility_statusBarLayerIsAlwaysVisible() { - checkResults(result -> LayersTraceSubject.assertThat(result) - .showsLayer(STATUS_BAR_WINDOW_TITLE).forAllEntries()); - } - - @Test public void checkVisibility_wallpaperLayerBecomesInvisible() { checkResults(result -> LayersTraceSubject.assertThat(result) - .showsLayer("wallpaper") + .showsLayer("Wallpaper") .then() - .hidesLayer("wallpaper") + .hidesLayer("Wallpaper") .forAllEntries()); } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenImeWindowTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenImeWindowTest.java index a81fa8e6d123..91d4a056d8fb 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenImeWindowTest.java +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenImeWindowTest.java @@ -17,28 +17,39 @@ package com.android.server.wm.flicker; import static com.android.server.wm.flicker.CommonTransitions.editTextSetFocus; -import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds; +import androidx.test.InstrumentationRegistry; import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; + +import com.android.server.wm.flicker.helpers.ImeAppHelper; import org.junit.Before; +import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.junit.runners.Parameterized; /** * Test IME window opening transitions. * To run this test: {@code atest FlickerTests:OpenImeWindowTest} */ @LargeTest -@RunWith(AndroidJUnit4.class) -public class OpenImeWindowTest extends FlickerTestBase { +@RunWith(Parameterized.class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class OpenImeWindowTest extends NonRotationTestBase { private static final String IME_WINDOW_TITLE = "InputMethod"; + public OpenImeWindowTest(String beginRotationName, int beginRotation) { + super(beginRotationName, beginRotation); + + mTestApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation()); + } + @Before public void runTransition() { - super.runTransition(editTextSetFocus(uiDevice) + run(editTextSetFocus((ImeAppHelper) mTestApp, mUiDevice, mBeginRotation) .includeJankyRuns().build()); } @@ -59,10 +70,4 @@ public class OpenImeWindowTest extends FlickerTestBase { .showsLayer(IME_WINDOW_TITLE) .forAllEntries()); } - - @Test - public void checkCoveredRegion_noUncoveredRegions() { - checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion( - getDisplayBounds()).forAllEntries()); - } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ResizeSplitScreenTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/ResizeSplitScreenTest.java index 50dba81e53b7..29b624005495 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ResizeSplitScreenTest.java +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ResizeSplitScreenTest.java @@ -24,64 +24,62 @@ import static com.android.server.wm.flicker.WindowUtils.getNavigationBarHeight; import static com.google.common.truth.Truth.assertThat; import android.graphics.Rect; -import android.platform.helpers.IAppHelper; import android.util.Rational; import androidx.test.InstrumentationRegistry; +import androidx.test.filters.FlakyTest; import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; + +import com.android.server.wm.flicker.helpers.ImeAppHelper; import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.junit.runners.Parameterized; /** * Test split screen resizing window transitions. * To run this test: {@code atest FlickerTests:ResizeSplitScreenTest} */ @LargeTest -@RunWith(AndroidJUnit4.class) -public class ResizeSplitScreenTest extends FlickerTestBase { +@RunWith(Parameterized.class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@FlakyTest(bugId = 140854698) +@Ignore("Waiting bug feedback") +public class ResizeSplitScreenTest extends NonRotationTestBase { + + private static String sSimpleActivity = "SimpleActivity"; + private static String sImeActivity = "ImeActivity"; - public ResizeSplitScreenTest() { - this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), + public ResizeSplitScreenTest(String beginRotationName, int beginRotation) { + super(beginRotationName, beginRotation); + + this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), "com.android.server.wm.flicker.testapp", "SimpleApp"); } @Before public void runTransition() { - IAppHelper bottomApp = new StandardAppHelper(InstrumentationRegistry - .getInstrumentation(), - "com.android.server.wm.flicker.testapp", "ImeApp"); - super.runTransition(resizeSplitScreen(testApp, bottomApp, uiDevice, new Rational(1, 3), - new Rational(2, 3)).includeJankyRuns().build()); - } - - @Test - public void checkVisibility_navBarLayerIsAlwaysVisible() { - checkResults(result -> LayersTraceSubject.assertThat(result) - .showsLayer(NAVIGATION_BAR_WINDOW_TITLE) - .forAllEntries()); - } - - @Test - public void checkVisibility_statusBarLayerIsAlwaysVisible() { - checkResults(result -> LayersTraceSubject.assertThat(result) - .showsLayer(STATUS_BAR_WINDOW_TITLE) - .forAllEntries()); + ImeAppHelper bottomApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation()); + run(resizeSplitScreen(mTestApp, bottomApp, mUiDevice, mBeginRotation, + new Rational(1, 3), new Rational(2, 3)) + .includeJankyRuns().build()); } @Test public void checkVisibility_topAppLayerIsAlwaysVisible() { checkResults(result -> LayersTraceSubject.assertThat(result) - .showsLayer("SimpleActivity") + .showsLayer(sSimpleActivity) .forAllEntries()); } @Test public void checkVisibility_bottomAppLayerIsAlwaysVisible() { checkResults(result -> LayersTraceSubject.assertThat(result) - .showsLayer("ImeActivity") + .showsLayer(sImeActivity) .forAllEntries()); } @@ -142,11 +140,11 @@ public class ResizeSplitScreenTest extends FlickerTestBase { displayBounds.bottom - getNavigationBarHeight()); LayersTraceSubject.assertThat(result) - .hasVisibleRegion("SimpleActivity", startingTopAppBounds) + .hasVisibleRegion(sSimpleActivity, startingTopAppBounds) .atTheEnd(); LayersTraceSubject.assertThat(result) - .hasVisibleRegion("ImeActivity", startingBottomAppBounds) + .hasVisibleRegion(sImeActivity, startingBottomAppBounds) .atTheEnd(); }); } @@ -168,14 +166,14 @@ public class ResizeSplitScreenTest extends FlickerTestBase { @Test public void checkVisibility_topAppWindowIsAlwaysVisible() { checkResults(result -> WmTraceSubject.assertThat(result) - .showsAppWindow("SimpleActivity") + .showsAppWindow(sSimpleActivity) .forAllEntries()); } @Test public void checkVisibility_bottomAppWindowIsAlwaysVisible() { checkResults(result -> WmTraceSubject.assertThat(result) - .showsAppWindow("ImeActivity") + .showsAppWindow(sImeActivity) .forAllEntries()); } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/SeamlessAppRotationTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/SeamlessAppRotationTest.java index 117ac5a8fadf..37d7c4ca2b46 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/SeamlessAppRotationTest.java +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/SeamlessAppRotationTest.java @@ -30,11 +30,15 @@ import android.graphics.Rect; import android.view.Surface; import androidx.test.InstrumentationRegistry; +import androidx.test.filters.FlakyTest; import androidx.test.filters.LargeTest; import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @@ -47,6 +51,9 @@ import java.util.Collection; */ @LargeTest @RunWith(Parameterized.class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@FlakyTest(bugId = 147659548) +@Ignore("Waiting bug feedback") public class SeamlessAppRotationTest extends FlickerTestBase { private int mBeginRotation; private int mEndRotation; @@ -62,7 +69,7 @@ public class SeamlessAppRotationTest extends FlickerTestBase { @Parameters(name = "{0}") public static Collection<Object[]> getParams() { int[] supportedRotations = - {Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270}; + {Surface.ROTATION_0, Surface.ROTATION_90}; Collection<Object[]> params = new ArrayList<>(); ArrayList<Intent> testIntents = new ArrayList<>(); @@ -105,7 +112,7 @@ public class SeamlessAppRotationTest extends FlickerTestBase { super.runTransition( changeAppRotation(mIntent, intentId, InstrumentationRegistry.getContext(), - uiDevice, mBeginRotation, mEndRotation).repeat(5).build()); + mUiDevice, mBeginRotation, mEndRotation).build()); } @Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/SplitScreenToLauncherTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/SplitScreenToLauncherTest.java index 1d30df9750b2..85a14941a7fd 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/SplitScreenToLauncherTest.java +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/SplitScreenToLauncherTest.java @@ -25,8 +25,11 @@ import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; /** * Test open app to split screen. @@ -34,16 +37,19 @@ import org.junit.runner.RunWith; */ @LargeTest @RunWith(AndroidJUnit4.class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@FlakyTest(bugId = 140856143) +@Ignore("Waiting bug feedback") public class SplitScreenToLauncherTest extends FlickerTestBase { public SplitScreenToLauncherTest() { - this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), + this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), "com.android.server.wm.flicker.testapp", "SimpleApp"); } @Before public void runTransition() { - super.runTransition(splitScreenToLauncher(testApp, uiDevice).includeJankyRuns().build()); + super.runTransition(splitScreenToLauncher(mTestApp, mUiDevice).includeJankyRuns().build()); } @Test @@ -62,13 +68,12 @@ public class SplitScreenToLauncherTest extends FlickerTestBase { .forAllEntries()); } - @FlakyTest(bugId = 79686616) @Test public void checkVisibility_appLayerBecomesInVisible() { checkResults(result -> LayersTraceSubject.assertThat(result) - .showsLayer(testApp.getPackage()) + .showsLayer(mTestApp.getPackage()) .then() - .hidesLayer(testApp.getPackage()) + .hidesLayer(mTestApp.getPackage()) .forAllEntries()); } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/StandardAppHelper.java b/tests/FlickerTests/src/com/android/server/wm/flicker/StandardAppHelper.java deleted file mode 100644 index 79a0220e0e87..000000000000 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/StandardAppHelper.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker; - -import android.app.Instrumentation; -import android.platform.helpers.AbstractStandardAppHelper; - -/** - * Class to take advantage of {@code IAppHelper} interface so the same test can be run against - * first party and third party apps. - */ -public class StandardAppHelper extends AbstractStandardAppHelper { - private final String mPackageName; - private final String mLauncherName; - - public StandardAppHelper(Instrumentation instr, String packageName, String launcherName) { - super(instr); - mPackageName = packageName; - mLauncherName = launcherName; - } - - /** - * {@inheritDoc} - */ - @Override - public String getPackage() { - return mPackageName; - } - - /** - * {@inheritDoc} - */ - @Override - public String getLauncherName() { - return mLauncherName; - } - - /** - * {@inheritDoc} - */ - @Override - public void dismissInitialDialogs() { - - } -} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerAppHelper.java b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerAppHelper.java new file mode 100644 index 000000000000..42977f549162 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerAppHelper.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.helpers; + +import android.app.Instrumentation; + +import com.android.server.wm.flicker.StandardAppHelper; + +public abstract class FlickerAppHelper extends StandardAppHelper { + + static int sFindTimeout = 10000; + static String sFlickerPackage = "com.android.server.wm.flicker.testapp"; + + public FlickerAppHelper(Instrumentation instr, String launcherName) { + super(instr, sFlickerPackage, launcherName); + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.java b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.java new file mode 100644 index 000000000000..56e1118590ea --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.helpers; + +import android.app.Instrumentation; +import android.support.test.uiautomator.UiDevice; + +public class ImeAppAutoFocusHelper extends ImeAppHelper { + + public ImeAppAutoFocusHelper(Instrumentation instr) { + super(instr, "ImeAppAutoFocus"); + } + + public void clickEditTextWidget(UiDevice device) { + // do nothing (the app is focused automatically) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.java b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.java new file mode 100644 index 000000000000..098fd6d4250b --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.helpers; + +import static android.os.SystemClock.sleep; + +import android.app.Instrumentation; +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.UiDevice; +import android.support.test.uiautomator.UiObject2; + +public class ImeAppHelper extends FlickerAppHelper { + + ImeAppHelper(Instrumentation instr, String launcherName) { + super(instr, launcherName); + } + + public ImeAppHelper(Instrumentation instr) { + this(instr, "ImeApp"); + } + + public void clickEditTextWidget(UiDevice device) { + UiObject2 editText = device.findObject(By.res(getPackage(), "plain_text_input")); + editText.click(); + sleep(500); + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.java b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.java new file mode 100644 index 000000000000..d00e11b2994d --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.helpers; + +import static com.android.server.wm.flicker.helpers.AutomationUtils.getPipWindowSelector; + +import android.app.Instrumentation; +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.UiDevice; +import android.support.test.uiautomator.UiObject2; +import android.support.test.uiautomator.Until; + +public class PipAppHelper extends FlickerAppHelper { + + public PipAppHelper(Instrumentation instr) { + super(instr, "PipApp"); + } + + public void clickEnterPipButton(UiDevice device) { + UiObject2 enterPipButton = device.findObject(By.res(getPackage(), "enter_pip")); + enterPipButton.click(); + UiObject2 pipWindow = device.wait(Until.findObject(getPipWindowSelector()), sFindTimeout); + + if (pipWindow == null) { + throw new RuntimeException("Unable to find PIP window"); + } + } + +} diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index b694172d60ca..0fe968273567 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -38,6 +38,15 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + <activity android:name=".ImeActivityAutoFocus" + android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivityAutoFocus" + android:windowSoftInputMode="stateVisible" + android:label="ImeAppAutoFocus"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> <activity android:name=".PipActivity" android:resizeableActivity="true" android:supportsPictureInPicture="true" diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml index d5eb02330441..4708cfd48381 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml @@ -18,6 +18,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" + android:focusableInTouchMode="true" android:background="@android:color/holo_green_light"> <EditText android:id="@+id/plain_text_input" android:layout_height="wrap_content" diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java new file mode 100644 index 000000000000..05da717620aa --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.testapp; + +import android.widget.EditText; + +public class ImeActivityAutoFocus extends ImeActivity { + + @Override + protected void onStart() { + super.onStart(); + + EditText editTextField = findViewById(R.id.plain_text_input); + editTextField.requestFocus(); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java index 3a0c1c9382fe..5cf81cb90fbc 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java @@ -17,7 +17,6 @@ package com.android.server.wm.flicker.testapp; import static android.os.SystemClock.sleep; -import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; import static com.android.server.wm.flicker.testapp.ActivityOptions.EXTRA_STARVE_UI_THREAD; @@ -39,8 +38,8 @@ public class SeamlessRotationActivity extends Activity { super.onCreate(savedInstanceState); enableSeamlessRotation(); setContentView(R.layout.activity_simple); - boolean starveUiThread = getIntent().getExtras() != null && - getIntent().getExtras().getBoolean(EXTRA_STARVE_UI_THREAD); + boolean starveUiThread = getIntent().getExtras() != null + && getIntent().getExtras().getBoolean(EXTRA_STARVE_UI_THREAD); if (starveUiThread) { starveUiThread(); } diff --git a/tests/FrameworkPerf/res/drawable-161dpi/wallpaper_goldengate_scale.jpg b/tests/FrameworkPerf/res/drawable-161dpi/wallpaper_goldengate_scale.jpg Binary files differindex 2271091909bc..8b7c6db267b9 100644 --- a/tests/FrameworkPerf/res/drawable-161dpi/wallpaper_goldengate_scale.jpg +++ b/tests/FrameworkPerf/res/drawable-161dpi/wallpaper_goldengate_scale.jpg diff --git a/tests/FrameworkPerf/res/drawable-nodpi/wallpaper_goldengate.jpg b/tests/FrameworkPerf/res/drawable-nodpi/wallpaper_goldengate.jpg Binary files differindex 2271091909bc..8b7c6db267b9 100644 --- a/tests/FrameworkPerf/res/drawable-nodpi/wallpaper_goldengate.jpg +++ b/tests/FrameworkPerf/res/drawable-nodpi/wallpaper_goldengate.jpg diff --git a/tests/GamePerformance/AndroidManifest.xml b/tests/GamePerformance/AndroidManifest.xml index b331e2c07e14..2ff7fa65664e 100644 --- a/tests/GamePerformance/AndroidManifest.xml +++ b/tests/GamePerformance/AndroidManifest.xml @@ -16,7 +16,9 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android.gameperformance"> + package="android.gameperformance" + android:versionCode="3" + android:versionName="3.0" > <uses-sdk android:minSdkVersion="25"/> <uses-feature android:glEsVersion="0x00020000" android:required="true" /> @@ -24,7 +26,8 @@ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <application android:theme="@style/noeffects"> <uses-library android:name="android.test.runner" /> - <activity android:name="android.gameperformance.GamePerformanceActivity" > + <activity android:name="android.gameperformance.GamePerformanceActivity" + android:screenOrientation="landscape" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> diff --git a/tests/GamePerformance/res/drawable/animation.xml b/tests/GamePerformance/res/drawable/animation.xml new file mode 100644 index 000000000000..b423ff0d1ee4 --- /dev/null +++ b/tests/GamePerformance/res/drawable/animation.xml @@ -0,0 +1,29 @@ +<!-- + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + --> + +<animation-list xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/animation" android:oneshot="false"> + <item android:drawable="@drawable/digit_0" android:duration="15" /> + <item android:drawable="@drawable/digit_1" android:duration="15" /> + <item android:drawable="@drawable/digit_2" android:duration="15" /> + <item android:drawable="@drawable/digit_3" android:duration="15" /> + <item android:drawable="@drawable/digit_4" android:duration="15" /> + <item android:drawable="@drawable/digit_5" android:duration="15" /> + <item android:drawable="@drawable/digit_6" android:duration="15" /> + <item android:drawable="@drawable/digit_7" android:duration="15" /> + <item android:drawable="@drawable/digit_8" android:duration="15" /> + <item android:drawable="@drawable/digit_9" android:duration="15" /> + </animation-list>
\ No newline at end of file diff --git a/tests/GamePerformance/res/drawable/digit_0.png b/tests/GamePerformance/res/drawable/digit_0.png Binary files differnew file mode 100644 index 000000000000..7264e3ee3771 --- /dev/null +++ b/tests/GamePerformance/res/drawable/digit_0.png diff --git a/tests/GamePerformance/res/drawable/digit_1.png b/tests/GamePerformance/res/drawable/digit_1.png Binary files differnew file mode 100644 index 000000000000..f098a71a4ab4 --- /dev/null +++ b/tests/GamePerformance/res/drawable/digit_1.png diff --git a/tests/GamePerformance/res/drawable/digit_2.png b/tests/GamePerformance/res/drawable/digit_2.png Binary files differnew file mode 100644 index 000000000000..f08cd31b4118 --- /dev/null +++ b/tests/GamePerformance/res/drawable/digit_2.png diff --git a/tests/GamePerformance/res/drawable/digit_3.png b/tests/GamePerformance/res/drawable/digit_3.png Binary files differnew file mode 100644 index 000000000000..497df8a9f473 --- /dev/null +++ b/tests/GamePerformance/res/drawable/digit_3.png diff --git a/tests/GamePerformance/res/drawable/digit_4.png b/tests/GamePerformance/res/drawable/digit_4.png Binary files differnew file mode 100644 index 000000000000..10efe8cf11b2 --- /dev/null +++ b/tests/GamePerformance/res/drawable/digit_4.png diff --git a/tests/GamePerformance/res/drawable/digit_5.png b/tests/GamePerformance/res/drawable/digit_5.png Binary files differnew file mode 100644 index 000000000000..1018a2fad733 --- /dev/null +++ b/tests/GamePerformance/res/drawable/digit_5.png diff --git a/tests/GamePerformance/res/drawable/digit_6.png b/tests/GamePerformance/res/drawable/digit_6.png Binary files differnew file mode 100644 index 000000000000..593c467d1529 --- /dev/null +++ b/tests/GamePerformance/res/drawable/digit_6.png diff --git a/tests/GamePerformance/res/drawable/digit_7.png b/tests/GamePerformance/res/drawable/digit_7.png Binary files differnew file mode 100644 index 000000000000..041b95fd8748 --- /dev/null +++ b/tests/GamePerformance/res/drawable/digit_7.png diff --git a/tests/GamePerformance/res/drawable/digit_8.png b/tests/GamePerformance/res/drawable/digit_8.png Binary files differnew file mode 100644 index 000000000000..f8fa4969cb28 --- /dev/null +++ b/tests/GamePerformance/res/drawable/digit_8.png diff --git a/tests/GamePerformance/res/drawable/digit_9.png b/tests/GamePerformance/res/drawable/digit_9.png Binary files differnew file mode 100644 index 000000000000..303b1da3d3f8 --- /dev/null +++ b/tests/GamePerformance/res/drawable/digit_9.png diff --git a/tests/GamePerformance/res/drawable/logo.png b/tests/GamePerformance/res/drawable/logo.png Binary files differnew file mode 100644 index 000000000000..61391df52077 --- /dev/null +++ b/tests/GamePerformance/res/drawable/logo.png diff --git a/tests/GamePerformance/src/android/gameperformance/BaseTest.java b/tests/GamePerformance/src/android/gameperformance/BaseTest.java new file mode 100644 index 000000000000..e7057565499c --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/BaseTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.gameperformance; + +import java.text.DecimalFormat; +import java.util.concurrent.TimeUnit; + +import android.annotation.NonNull; +import android.content.Context; +import android.util.Log; + +/** + * Base class for a test that performs bisection to determine maximum + * performance of a metric test measures. + */ +public abstract class BaseTest { + private final static String TAG = "BaseTest"; + + // Time to wait for render warm up. No statistics is collected during this pass. + private final static long WARM_UP_TIME = TimeUnit.SECONDS.toMillis(5); + + // Perform pass to probe the configuration using iterations. After each iteration current FPS is + // checked and if it looks obviously bad, pass gets stopped earlier. Once all iterations are + // done and final FPS is above PASS_THRESHOLD pass to probe is considered successful. + private final static long TEST_ITERATION_TIME = TimeUnit.SECONDS.toMillis(12); + private final static int TEST_ITERATION_COUNT = 5; + + // FPS pass test threshold, in ratio from ideal FPS, that matches device + // refresh rate. + private final static double PASS_THRESHOLD = 0.95; + // FPS threshold, in ratio from ideal FPS, to identify that current pass to probe is obviously + // bad and to stop pass earlier. + private final static double OBVIOUS_BAD_THRESHOLD = 0.90; + + private static DecimalFormat DOUBLE_FORMATTER = new DecimalFormat("#.##"); + + private final GamePerformanceActivity mActivity; + + // Device's refresh rate. + private final double mRefreshRate; + + public BaseTest(@NonNull GamePerformanceActivity activity) { + mActivity = activity; + mRefreshRate = activity.getDisplay().getRefreshRate(); + } + + @NonNull + public Context getContext() { + return mActivity; + } + + @NonNull + public GamePerformanceActivity getActivity() { + return mActivity; + } + + // Returns name of the test. + public abstract String getName(); + + // Returns unit name. + public abstract String getUnitName(); + + // Returns number of measured units per one bisection unit. + public abstract double getUnitScale(); + + // Initializes test. + public abstract void initUnits(double unitCount); + + // Initializes probe pass. + protected abstract void initProbePass(int probe); + + // Frees probe pass. + protected abstract void freeProbePass(); + + /** + * Performs the test and returns maximum number of measured units achieved. Unit is test + * specific and name is returned by getUnitName. Returns 0 in case of failure. + */ + public double run() { + try { + Log.i(TAG, "Test started " + getName()); + + final double passFps = PASS_THRESHOLD * mRefreshRate; + final double obviousBadFps = OBVIOUS_BAD_THRESHOLD * mRefreshRate; + + // Bisection bounds. Probe value is taken as middle point. Then it used to initialize + // test with probe * getUnitScale units. In case probe passed, lowLimit is updated to + // probe, otherwise upLimit is updated to probe. lowLimit contains probe that passes + // and upLimit contains the probe that fails. Each iteration narrows the range. + // Iterations continue until range is collapsed and lowLimit contains actual test + // result. + int lowLimit = 0; // Initially 0, that is recognized as failure. + int upLimit = 250; + + while (true) { + int probe = (lowLimit + upLimit) / 2; + if (probe == lowLimit) { + Log.i(TAG, "Test done: " + DOUBLE_FORMATTER.format(probe * getUnitScale()) + + " " + getUnitName()); + return probe * getUnitScale(); + } + + Log.i(TAG, "Start probe: " + DOUBLE_FORMATTER.format(probe * getUnitScale()) + " " + + getUnitName()); + initProbePass(probe); + + Thread.sleep(WARM_UP_TIME); + + getActivity().resetFrameTimes(); + + double fps = 0.0f; + for (int i = 0; i < TEST_ITERATION_COUNT; ++i) { + Thread.sleep(TEST_ITERATION_TIME); + fps = getActivity().getFps(); + if (fps < obviousBadFps) { + // Stop test earlier, we could not fit the loading. + break; + } + } + + freeProbePass(); + + Log.i(TAG, "Finish probe: " + DOUBLE_FORMATTER.format(probe * getUnitScale()) + + " " + getUnitName() + " - " + DOUBLE_FORMATTER.format(fps) + " FPS."); + if (fps < passFps) { + upLimit = probe; + } else { + lowLimit = probe; + } + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return 0; + } + } +}
\ No newline at end of file diff --git a/tests/GamePerformance/src/android/gameperformance/CPULoadThread.java b/tests/GamePerformance/src/android/gameperformance/CPULoadThread.java new file mode 100644 index 000000000000..fa6f03bf88cf --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/CPULoadThread.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.gameperformance; + +/** + * Ballast thread that emulates CPU load by performing heavy computation in loop. + */ +public class CPULoadThread extends Thread { + private boolean mStopRequest; + + public CPULoadThread() { + mStopRequest = false; + } + + private static double computePi() { + double accumulator = 0; + double prevAccumulator = -1; + int index = 1; + while (true) { + accumulator += ((1.0 / (2.0 * index - 1)) - (1.0 / (2.0 * index + 1))); + if (accumulator == prevAccumulator) { + break; + } + prevAccumulator = accumulator; + index += 2; + } + return 4 * accumulator; + } + + // Requests thread to stop. + public void issueStopRequest() { + synchronized (this) { + mStopRequest = true; + } + } + + @Override + public void run() { + // Load CPU by PI computation. + while (computePi() != 0) { + synchronized (this) { + if (mStopRequest) { + break; + } + } + } + } +} diff --git a/tests/GamePerformance/src/android/gameperformance/ControlsTest.java b/tests/GamePerformance/src/android/gameperformance/ControlsTest.java new file mode 100644 index 000000000000..6c36ddcc620d --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/ControlsTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.gameperformance; + +import android.annotation.NonNull; + +/** + * Tests that verifies how many UI controls can be handled to keep FPS close to device refresh rate. + * As a test UI control ImageView with an infinite animation is chosen. The animation has refresh + * rate ~67Hz that forces all devices to refresh UI at highest possible rate. + */ +public class ControlsTest extends BaseTest { + public ControlsTest(@NonNull GamePerformanceActivity activity) { + super(activity); + } + + @NonNull + public CustomControlView getView() { + return getActivity().getControlView(); + } + + @Override + protected void initProbePass(int probe) { + try { + getActivity().attachControlView(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + initUnits(probe * getUnitScale()); + } + + @Override + protected void freeProbePass() { + } + + @Override + public String getName() { + return "control_count"; + } + + @Override + public String getUnitName() { + return "controls"; + } + + @Override + public double getUnitScale() { + return 5.0; + } + + @Override + public void initUnits(double controlCount) { + try { + getView().createControls(getActivity(), (int)Math.round(controlCount)); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } +}
\ No newline at end of file diff --git a/tests/GamePerformance/src/android/gameperformance/CustomControlView.java b/tests/GamePerformance/src/android/gameperformance/CustomControlView.java new file mode 100644 index 000000000000..8d11a416c8a8 --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/CustomControlView.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.gameperformance; + +import android.annotation.MainThread; +import android.annotation.NonNull; +import android.annotation.WorkerThread; +import android.app.Activity; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.drawable.AnimationDrawable; +import android.view.WindowManager; +import android.widget.AbsoluteLayout; +import android.widget.ImageView; +import android.window.WindowMetricsHelper; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +/** + * View that holds requested number of UI controls as ImageView with an infinite animation. + */ +public class CustomControlView extends AbsoluteLayout { + private final static int CONTROL_DIMENSION = 48; + + private final int mPerRowControlCount; + private List<Long> mFrameTimes = new ArrayList<>(); + + public CustomControlView(@NonNull Context context) { + super(context); + + final WindowManager wm = context.getSystemService(WindowManager.class); + final int width = WindowMetricsHelper.getBoundsExcludingNavigationBarAndCutout( + wm.getCurrentWindowMetrics()).width(); + mPerRowControlCount = width / CONTROL_DIMENSION; + } + + /** + * Helper class that overrides ImageView and observes draw requests. Only + * one such control is created which is the first control in the view. + */ + class ReferenceImageView extends ImageView { + public ReferenceImageView(Context context) { + super(context); + } + @Override + public void draw(Canvas canvas) { + reportFrame(); + super.draw(canvas); + } + } + + @WorkerThread + public void createControls( + @NonNull Activity activity, int controlCount) throws InterruptedException { + synchronized (this) { + final CountDownLatch latch = new CountDownLatch(1); + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + removeAllViews(); + + for (int i = 0; i < controlCount; ++i) { + final ImageView image = (i == 0) ? + new ReferenceImageView(activity) : new ImageView(activity); + final int x = (i % mPerRowControlCount) * CONTROL_DIMENSION; + final int y = (i / mPerRowControlCount) * CONTROL_DIMENSION; + final AbsoluteLayout.LayoutParams layoutParams = + new AbsoluteLayout.LayoutParams( + CONTROL_DIMENSION, CONTROL_DIMENSION, x, y); + image.setLayoutParams(layoutParams); + image.setBackgroundResource(R.drawable.animation); + final AnimationDrawable animation = + (AnimationDrawable)image.getBackground(); + animation.start(); + addView(image); + } + + latch.countDown(); + } + }); + latch.await(); + } + } + + @MainThread + private void reportFrame() { + final long time = System.currentTimeMillis(); + synchronized (mFrameTimes) { + mFrameTimes.add(time); + } + } + + /** + * Resets frame times in order to calculate FPS for the different test pass. + */ + public void resetFrameTimes() { + synchronized (mFrameTimes) { + mFrameTimes.clear(); + } + } + + /** + * Returns current FPS based on collected frame times. + */ + public double getFps() { + synchronized (mFrameTimes) { + if (mFrameTimes.size() < 2) { + return 0.0f; + } + return 1000.0 * mFrameTimes.size() / + (mFrameTimes.get(mFrameTimes.size() - 1) - mFrameTimes.get(0)); + } + } +}
\ No newline at end of file diff --git a/tests/GamePerformance/src/android/gameperformance/CustomOpenGLView.java b/tests/GamePerformance/src/android/gameperformance/CustomOpenGLView.java index 2b37280ae9b5..08697ae95376 100644 --- a/tests/GamePerformance/src/android/gameperformance/CustomOpenGLView.java +++ b/tests/GamePerformance/src/android/gameperformance/CustomOpenGLView.java @@ -17,23 +17,36 @@ package android.gameperformance; import java.util.ArrayList; import java.util.List; -import java.util.Random; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.opengl.GLES20; import android.opengl.GLSurfaceView; +import android.util.Log; public class CustomOpenGLView extends GLSurfaceView { - private Random mRandom; - private List<Long> mFrameTimes; + public final static String TAG = "CustomOpenGLView"; - public CustomOpenGLView(Context context) { + private final List<Long> mFrameTimes; + private final Object mLock = new Object(); + private boolean mRenderReady = false; + private FrameDrawer mFrameDrawer = null; + + private float mRenderRatio; + private int mRenderWidth; + private int mRenderHeight; + + public interface FrameDrawer { + public void drawFrame(@NonNull GL10 gl); + } + + public CustomOpenGLView(@NonNull Context context) { super(context); - mRandom = new Random(); mFrameTimes = new ArrayList<Long>(); setEGLContextClientVersion(2); @@ -41,25 +54,35 @@ public class CustomOpenGLView extends GLSurfaceView { setRenderer(new GLSurfaceView.Renderer() { @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { + Log.i(TAG, "SurfaceCreated: " + config); GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f); gl.glClearDepthf(1.0f); - gl.glEnable(GL10.GL_DEPTH_TEST); + gl.glDisable(GL10.GL_DEPTH_TEST); gl.glDepthFunc(GL10.GL_LEQUAL); gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, - GL10.GL_NICEST); } + GL10.GL_NICEST); + synchronized (mLock) { + mRenderReady = true; + mLock.notify(); + } + } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { + Log.i(TAG, "SurfaceChanged: " + width + "x" + height); GLES20.glViewport(0, 0, width, height); + setRenderBounds(width, height); } @Override public void onDrawFrame(GL10 gl) { - GLES20.glClearColor( - mRandom.nextFloat(), mRandom.nextFloat(), mRandom.nextFloat(), 1.0f); + GLES20.glClearColor(0.25f, 0.25f, 0.25f, 1.0f); gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); - synchronized (mFrameTimes) { + synchronized (mLock) { + if (mFrameDrawer != null) { + mFrameDrawer.drawFrame(gl); + } mFrameTimes.add(System.currentTimeMillis()); } } @@ -67,20 +90,38 @@ public class CustomOpenGLView extends GLSurfaceView { setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); } + public void setRenderBounds(int width, int height) { + mRenderWidth = width; + mRenderHeight = height; + mRenderRatio = (float) mRenderWidth / mRenderHeight; + } + + public float getRenderRatio() { + return mRenderRatio; + } + + public int getRenderWidth() { + return mRenderWidth; + } + + public int getRenderHeight() { + return mRenderHeight; + } + /** - * Resets frame times in order to calculate fps for different test pass. + * Resets frame times in order to calculate FPS for the different test pass. */ public void resetFrameTimes() { - synchronized (mFrameTimes) { + synchronized (mLock) { mFrameTimes.clear(); } } /** - * Returns current fps based on collected frame times. + * Returns current FPS based on collected frame times. */ public double getFps() { - synchronized (mFrameTimes) { + synchronized (mLock) { if (mFrameTimes.size() < 2) { return 0.0f; } @@ -88,4 +129,26 @@ public class CustomOpenGLView extends GLSurfaceView { (mFrameTimes.get(mFrameTimes.size() - 1) - mFrameTimes.get(0)); } } + + /** + * Waits for render attached to the view. + */ + public void waitRenderReady() { + synchronized (mLock) { + while (!mRenderReady) { + try { + mLock.wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + } + + /** + * Sets/resets frame drawer. + */ + public void setFrameDrawer(@Nullable FrameDrawer frameDrawer) { + mFrameDrawer = frameDrawer; + } } diff --git a/tests/GamePerformance/src/android/gameperformance/DeviceCallsOpenGLTest.java b/tests/GamePerformance/src/android/gameperformance/DeviceCallsOpenGLTest.java new file mode 100644 index 000000000000..df2ae5cf670a --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/DeviceCallsOpenGLTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.gameperformance; + +import java.util.ArrayList; +import java.util.List; + +import android.annotation.NonNull; + +/** + * Tests that verifies maximum number of device calls to render the geometry to keep FPS close to + * the device refresh rate. This uses trivial one triangle patch that is rendered multiple times. + */ +public class DeviceCallsOpenGLTest extends RenderPatchOpenGLTest { + + public DeviceCallsOpenGLTest(@NonNull GamePerformanceActivity activity) { + super(activity); + } + + @Override + public String getName() { + return "device_calls"; + } + + @Override + public String getUnitName() { + return "calls"; + } + + @Override + public double getUnitScale() { + return 25.0; + } + + @Override + public void initUnits(double deviceCallsD) { + final List<RenderPatchAnimation> renderPatches = new ArrayList<>(); + final RenderPatch renderPatch = new RenderPatch(1 /* triangleCount */, + 0.05f /* dimension */, + RenderPatch.TESSELLATION_BASE); + final int deviceCalls = (int)Math.round(deviceCallsD); + for (int i = 0; i < deviceCalls; ++i) { + renderPatches.add(new RenderPatchAnimation(renderPatch, getView().getRenderRatio())); + } + setRenderPatches(renderPatches); + } +}
\ No newline at end of file diff --git a/tests/GamePerformance/src/android/gameperformance/FillRateOpenGLTest.java b/tests/GamePerformance/src/android/gameperformance/FillRateOpenGLTest.java new file mode 100644 index 000000000000..9b2619372d16 --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/FillRateOpenGLTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.gameperformance; + +import java.util.ArrayList; +import java.util.List; + +import javax.microedition.khronos.opengles.GL; + +import android.annotation.NonNull; +import android.opengl.GLES20; + +/** + * Tests that verifies maximum fill rate per frame can be used to keep FPS close to the device + * refresh rate. It works in two modes, blend disabled and blend enabled. This uses few big simple + * quad patches. + */ +public class FillRateOpenGLTest extends RenderPatchOpenGLTest { + private final float[] BLEND_COLOR = new float[] { 1.0f, 1.0f, 1.0f, 0.2f }; + + private final boolean mTestBlend; + + public FillRateOpenGLTest(@NonNull GamePerformanceActivity activity, boolean testBlend) { + super(activity); + mTestBlend = testBlend; + } + + @Override + public String getName() { + return mTestBlend ? "blend_rate" : "fill_rate"; + } + + @Override + public String getUnitName() { + return "screens"; + } + + @Override + public double getUnitScale() { + return 0.2; + } + + @Override + public void initUnits(double screens) { + final CustomOpenGLView view = getView(); + final int pixelRate = (int)Math.round(screens * view.getHeight() * view.getWidth()); + final int maxPerPath = view.getHeight() * view.getHeight(); + + final int patchCount = (int)(pixelRate + maxPerPath -1) / maxPerPath; + final float patchDimension = + (float)((Math.sqrt(2.0f) * pixelRate / patchCount) / maxPerPath); + + final List<RenderPatchAnimation> renderPatches = new ArrayList<>(); + final RenderPatch renderPatch = new RenderPatch(2 /* triangleCount for quad */, + patchDimension, + RenderPatch.TESSELLATION_BASE); + for (int i = 0; i < patchCount; ++i) { + renderPatches.add(new RenderPatchAnimation(renderPatch, getView().getRenderRatio())); + } + setRenderPatches(renderPatches); + } + + @Override + public float[] getColor() { + return BLEND_COLOR; + } + + @Override + public void onBeforeDraw(GL gl) { + if (!mTestBlend) { + return; + } + + // Enable blend if needed. + GLES20.glEnable(GLES20.GL_BLEND); + OpenGLUtils.checkGlError("disableBlend"); + GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); + OpenGLUtils.checkGlError("blendFunction"); + } +}
\ No newline at end of file diff --git a/tests/GamePerformance/src/android/gameperformance/GamePerformanceActivity.java b/tests/GamePerformance/src/android/gameperformance/GamePerformanceActivity.java index b0e6196b53d7..dc745f17e698 100644 --- a/tests/GamePerformance/src/android/gameperformance/GamePerformanceActivity.java +++ b/tests/GamePerformance/src/android/gameperformance/GamePerformanceActivity.java @@ -25,14 +25,32 @@ import android.view.WindowManager; import android.widget.RelativeLayout; /** - * Minimal activity that holds SurfaceView or GLSurfaceView. - * call attachSurfaceView or attachOpenGLView to switch views. + * Minimal activity that holds different types of views. + * call attachSurfaceView, attachOpenGLView or attachControlView to switch + * the view. */ public class GamePerformanceActivity extends Activity { private CustomSurfaceView mSurfaceView = null; private CustomOpenGLView mOpenGLView = null; + private CustomControlView mControlView = null; + private RelativeLayout mRootLayout; + private void detachAllViews() { + if (mOpenGLView != null) { + mRootLayout.removeView(mOpenGLView); + mOpenGLView = null; + } + if (mSurfaceView != null) { + mRootLayout.removeView(mSurfaceView); + mSurfaceView = null; + } + if (mControlView != null) { + mRootLayout.removeView(mControlView); + mControlView = null; + } + } + public void attachSurfaceView() throws InterruptedException { synchronized (mRootLayout) { if (mSurfaceView != null) { @@ -42,10 +60,7 @@ public class GamePerformanceActivity extends Activity { runOnUiThread(new Runnable() { @Override public void run() { - if (mOpenGLView != null) { - mRootLayout.removeView(mOpenGLView); - mOpenGLView = null; - } + detachAllViews(); mSurfaceView = new CustomSurfaceView(GamePerformanceActivity.this); mRootLayout.addView(mSurfaceView); latch.countDown(); @@ -65,10 +80,7 @@ public class GamePerformanceActivity extends Activity { runOnUiThread(new Runnable() { @Override public void run() { - if (mSurfaceView != null) { - mRootLayout.removeView(mSurfaceView); - mSurfaceView = null; - } + detachAllViews(); mOpenGLView = new CustomOpenGLView(GamePerformanceActivity.this); mRootLayout.addView(mOpenGLView); latch.countDown(); @@ -78,6 +90,40 @@ public class GamePerformanceActivity extends Activity { } } + public void attachControlView() throws InterruptedException { + synchronized (mRootLayout) { + if (mControlView != null) { + return; + } + final CountDownLatch latch = new CountDownLatch(1); + runOnUiThread(new Runnable() { + @Override + public void run() { + detachAllViews(); + mControlView = new CustomControlView(GamePerformanceActivity.this); + mRootLayout.addView(mControlView); + latch.countDown(); + } + }); + latch.await(); + } + } + + + public CustomOpenGLView getOpenGLView() { + if (mOpenGLView == null) { + throw new RuntimeException("OpenGL view is not attached"); + } + return mOpenGLView; + } + + public CustomControlView getControlView() { + if (mControlView == null) { + throw new RuntimeException("Control view is not attached"); + } + return mControlView; + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -105,6 +151,8 @@ public class GamePerformanceActivity extends Activity { mSurfaceView.resetFrameTimes(); } else if (mOpenGLView != null) { mOpenGLView.resetFrameTimes(); + } else if (mControlView != null) { + mControlView.resetFrameTimes(); } else { throw new IllegalStateException("Nothing attached"); } @@ -115,6 +163,8 @@ public class GamePerformanceActivity extends Activity { return mSurfaceView.getFps(); } else if (mOpenGLView != null) { return mOpenGLView.getFps(); + } else if (mControlView != null) { + return mControlView.getFps(); } else { throw new IllegalStateException("Nothing attached"); } diff --git a/tests/GamePerformance/src/android/gameperformance/GamePerformanceTest.java b/tests/GamePerformance/src/android/gameperformance/GamePerformanceTest.java index e5de7d75886e..d6e2861c03a7 100644 --- a/tests/GamePerformance/src/android/gameperformance/GamePerformanceTest.java +++ b/tests/GamePerformance/src/android/gameperformance/GamePerformanceTest.java @@ -17,14 +17,18 @@ package android.gameperformance; import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; +import android.annotation.NonNull; import android.app.Activity; import android.content.Context; import android.graphics.PixelFormat; import android.os.Build; import android.os.Bundle; +import android.os.Debug; import android.os.Trace; import android.test.ActivityInstrumentationTestCase2; import android.test.suitebuilder.annotation.SmallTest; @@ -84,4 +88,50 @@ public class GamePerformanceTest extends getInstrumentation().sendStatus(Activity.RESULT_OK, status); } + + @SmallTest + public void testPerformanceMetricsWithoutExtraLoad() throws IOException, InterruptedException { + final Bundle status = runPerformanceTests("no_extra_load_"); + getInstrumentation().sendStatus(Activity.RESULT_OK, status); + } + + @SmallTest + public void testPerformanceMetricsWithExtraLoad() throws IOException, InterruptedException { + // Start CPU ballast threads first. + CPULoadThread[] cpuLoadThreads = new CPULoadThread[2]; + for (int i = 0; i < cpuLoadThreads.length; ++i) { + cpuLoadThreads[i] = new CPULoadThread(); + cpuLoadThreads[i].start(); + } + + final Bundle status = runPerformanceTests("extra_load_"); + + for (int i = 0; i < cpuLoadThreads.length; ++i) { + cpuLoadThreads[i].issueStopRequest(); + cpuLoadThreads[i].join(); + } + + getInstrumentation().sendStatus(Activity.RESULT_OK, status); + } + + @NonNull + private Bundle runPerformanceTests(@NonNull String prefix) { + final Bundle status = new Bundle(); + + final GamePerformanceActivity activity = getActivity(); + + final List<BaseTest> tests = new ArrayList<>(); + tests.add(new TriangleCountOpenGLTest(activity)); + tests.add(new FillRateOpenGLTest(activity, false /* testBlend */)); + tests.add(new FillRateOpenGLTest(activity, true /* testBlend */)); + tests.add(new DeviceCallsOpenGLTest(activity)); + tests.add(new ControlsTest(activity)); + + for (BaseTest test : tests) { + final double result = test.run(); + status.putDouble(prefix + test.getName(), result); + } + + return status; + } } diff --git a/tests/GamePerformance/src/android/gameperformance/OpenGLTest.java b/tests/GamePerformance/src/android/gameperformance/OpenGLTest.java new file mode 100644 index 000000000000..1d3f95cdd5b8 --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/OpenGLTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.gameperformance; + +import javax.microedition.khronos.opengles.GL; +import javax.microedition.khronos.opengles.GL10; + +import android.annotation.NonNull; +import android.gameperformance.CustomOpenGLView.FrameDrawer; + +/** + * Base class for all OpenGL based tests. + */ +public abstract class OpenGLTest extends BaseTest { + public OpenGLTest(@NonNull GamePerformanceActivity activity) { + super(activity); + } + + @NonNull + public CustomOpenGLView getView() { + return getActivity().getOpenGLView(); + } + + // Performs test drawing. + protected abstract void draw(GL gl); + + // Initializes the test on first draw call. + private class ParamFrameDrawer implements FrameDrawer { + private final double mUnitCount; + private boolean mInited; + + public ParamFrameDrawer(double unitCount) { + mUnitCount = unitCount; + mInited = false; + } + + @Override + public void drawFrame(GL10 gl) { + if (!mInited) { + initUnits(mUnitCount); + mInited = true; + } + draw(gl); + } + } + + @Override + protected void initProbePass(int probe) { + try { + getActivity().attachOpenGLView(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + getView().waitRenderReady(); + getView().setFrameDrawer(new ParamFrameDrawer(probe * getUnitScale())); + } + + @Override + protected void freeProbePass() { + getView().setFrameDrawer(null); + } +}
\ No newline at end of file diff --git a/tests/GamePerformance/src/android/gameperformance/OpenGLUtils.java b/tests/GamePerformance/src/android/gameperformance/OpenGLUtils.java new file mode 100644 index 000000000000..4f98c52b4b39 --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/OpenGLUtils.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.gameperformance; + +import android.annotation.NonNull; +import android.content.Context; +import android.graphics.BitmapFactory; +import android.opengl.GLES20; +import android.opengl.GLUtils; +import android.util.Log; + +/** + * Helper class for OpenGL. + */ +public class OpenGLUtils { + private final static String TAG = "OpenGLUtils"; + + public static void checkGlError(String glOperation) { + final int error = GLES20.glGetError(); + if (error == GLES20.GL_NO_ERROR) { + return; + } + final String errorMessage = glOperation + ": glError " + error; + Log.e(TAG, errorMessage); + } + + public static int loadShader(int type, String shaderCode) { + final int shader = GLES20.glCreateShader(type); + checkGlError("createShader"); + + GLES20.glShaderSource(shader, shaderCode); + checkGlError("shaderSource"); + GLES20.glCompileShader(shader); + checkGlError("shaderCompile"); + + return shader; + } + + public static int createProgram(@NonNull String vertexShaderCode, + @NonNull String fragmentShaderCode) { + final int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode); + final int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode); + + final int program = GLES20.glCreateProgram(); + checkGlError("createProgram"); + GLES20.glAttachShader(program, vertexShader); + checkGlError("attachVertexShader"); + GLES20.glAttachShader(program, fragmentShader); + checkGlError("attachFragmentShader"); + GLES20.glLinkProgram(program); + checkGlError("linkProgram"); + + return program; + } + + public static int createTexture(@NonNull Context context, int resource) { + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inScaled = false; + + final int[] textureHandle = new int[1]; + GLES20.glGenTextures(1, textureHandle, 0); + OpenGLUtils.checkGlError("GenTextures"); + final int handle = textureHandle[0]; + + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, handle); + GLES20.glTexParameteri( + GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameteri( + GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + GLUtils.texImage2D( + GLES20.GL_TEXTURE_2D, + 0, + BitmapFactory.decodeResource( + context.getResources(), resource, options), + 0); + + return handle; + } +}
\ No newline at end of file diff --git a/tests/GamePerformance/src/android/gameperformance/RenderPatch.java b/tests/GamePerformance/src/android/gameperformance/RenderPatch.java new file mode 100644 index 000000000000..2e69a61475db --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/RenderPatch.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.gameperformance; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Helper class that generates patch to render. Patch is a regular polygon with the center in 0. + * Regular polygon fits in circle with requested radius. + */ +public class RenderPatch { + public static final int FLOAT_SIZE = 4; + public static final int SHORT_SIZE = 2; + public static final int VERTEX_COORD_COUNT = 3; + public static final int VERTEX_STRIDE = VERTEX_COORD_COUNT * FLOAT_SIZE; + public static final int TEXTURE_COORD_COUNT = 2; + public static final int TEXTURE_STRIDE = TEXTURE_COORD_COUNT * FLOAT_SIZE; + + // Tessellation is done using points on circle. + public static final int TESSELLATION_BASE = 0; + // Tesselation is done using extra point in 0. + public static final int TESSELLATION_TO_CENTER = 1; + + // Radius of circle that fits polygon. + private final float mDimension; + + private final ByteBuffer mVertexBuffer; + private final ByteBuffer mTextureBuffer; + private final ByteBuffer mIndexBuffer; + + public RenderPatch(int triangleCount, float dimension, int tessellation) { + mDimension = dimension; + + int pointCount; + int externalPointCount; + + if (triangleCount < 1) { + throw new IllegalArgumentException("Too few triangles to perform tessellation"); + } + + switch (tessellation) { + case TESSELLATION_BASE: + externalPointCount = triangleCount + 2; + pointCount = externalPointCount; + break; + case TESSELLATION_TO_CENTER: + if (triangleCount < 3) { + throw new IllegalArgumentException( + "Too few triangles to perform tessellation to center"); + } + externalPointCount = triangleCount; + pointCount = triangleCount + 1; + break; + default: + throw new IllegalArgumentException("Wrong tesselation requested"); + } + + if (pointCount > Short.MAX_VALUE) { + throw new IllegalArgumentException("Number of requested triangles is too big"); + } + + mVertexBuffer = ByteBuffer.allocateDirect(pointCount * VERTEX_STRIDE); + mVertexBuffer.order(ByteOrder.nativeOrder()); + + mTextureBuffer = ByteBuffer.allocateDirect(pointCount * TEXTURE_STRIDE); + mTextureBuffer.order(ByteOrder.nativeOrder()); + + for (int i = 0; i < externalPointCount; ++i) { + // Use 45 degree rotation to make quad aligned along axises in case + // triangleCount is four. + final double angle = Math.PI * 0.25 + (Math.PI * 2.0 * i) / (externalPointCount); + // Positions + mVertexBuffer.putFloat((float) (dimension * Math.sin(angle))); + mVertexBuffer.putFloat((float) (dimension * Math.cos(angle))); + mVertexBuffer.putFloat(0.0f); + // Texture coordinates. + mTextureBuffer.putFloat((float) (0.5 + 0.5 * Math.sin(angle))); + mTextureBuffer.putFloat((float) (0.5 - 0.5 * Math.cos(angle))); + } + + if (tessellation == TESSELLATION_TO_CENTER) { + // Add center point. + mVertexBuffer.putFloat(0.0f); + mVertexBuffer.putFloat(0.0f); + mVertexBuffer.putFloat(0.0f); + mTextureBuffer.putFloat(0.5f); + mTextureBuffer.putFloat(0.5f); + } + + mIndexBuffer = + ByteBuffer.allocateDirect( + triangleCount * 3 /* indices per triangle */ * SHORT_SIZE); + mIndexBuffer.order(ByteOrder.nativeOrder()); + + switch (tessellation) { + case TESSELLATION_BASE: + for (int i = 0; i < triangleCount; ++i) { + mIndexBuffer.putShort((short) 0); + mIndexBuffer.putShort((short) (i + 1)); + mIndexBuffer.putShort((short) (i + 2)); + } + break; + case TESSELLATION_TO_CENTER: + for (int i = 0; i < triangleCount; ++i) { + mIndexBuffer.putShort((short)i); + mIndexBuffer.putShort((short)((i + 1) % externalPointCount)); + mIndexBuffer.putShort((short)externalPointCount); + } + break; + } + + if (mVertexBuffer.remaining() != 0 || mTextureBuffer.remaining() != 0 || mIndexBuffer.remaining() != 0) { + throw new RuntimeException("Failed to fill buffers"); + } + + mVertexBuffer.position(0); + mTextureBuffer.position(0); + mIndexBuffer.position(0); + } + + public float getDimension() { + return mDimension; + } + + public ByteBuffer getVertexBuffer() { + return mVertexBuffer; + } + + public ByteBuffer getTextureBuffer() { + return mTextureBuffer; + } + + public ByteBuffer getIndexBuffer() { + return mIndexBuffer; + } +}
\ No newline at end of file diff --git a/tests/GamePerformance/src/android/gameperformance/RenderPatchAnimation.java b/tests/GamePerformance/src/android/gameperformance/RenderPatchAnimation.java new file mode 100644 index 000000000000..7dcdb00e1014 --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/RenderPatchAnimation.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.gameperformance; + +import java.util.Random; + +import android.annotation.NonNull; +import android.opengl.Matrix; + +/** + * Class that performs bouncing animation for RenderPatch on the screen. + */ +public class RenderPatchAnimation { + private final static Random RANDOM = new Random(); + + private final RenderPatch mRenderPatch; + // Bounds of animation + private final float mAvailableX; + private final float mAvailableY; + + // Crurrent position. + private float mPosX; + private float mPosY; + // Direction of movement. + private float mDirX; + private float mDirY; + + private float[] mMatrix; + + public RenderPatchAnimation(@NonNull RenderPatch renderPatch, float ratio) { + mRenderPatch = renderPatch; + + mAvailableX = ratio - mRenderPatch.getDimension(); + mAvailableY = 1.0f - mRenderPatch.getDimension(); + + mPosX = 2.0f * mAvailableX * RANDOM.nextFloat() - mAvailableX; + mPosY = 2.0f * mAvailableY * RANDOM.nextFloat() - mAvailableY; + mMatrix = new float[16]; + + // Evenly distributed in cycle, normalized. + while (true) { + mDirX = 2.0f * RANDOM.nextFloat() - 1.0f; + mDirY = mRenderPatch.getDimension() < 1.0f ? 2.0f * RANDOM.nextFloat() - 1.0f : 0.0f; + + final float length = (float)Math.sqrt(mDirX * mDirX + mDirY * mDirY); + if (length <= 1.0f && length > 0.0f) { + mDirX /= length; + mDirY /= length; + break; + } + } + } + + @NonNull + public RenderPatch getRenderPatch() { + return mRenderPatch; + } + + /** + * Performs the next update. t specifies the distance to travel along the direction. This checks + * if patch goes out of screen and invert axis direction if needed. + */ + public void update(float t) { + mPosX += mDirX * t; + mPosY += mDirY * t; + if (mPosX < -mAvailableX) { + mDirX = Math.abs(mDirX); + } else if (mPosX > mAvailableX) { + mDirX = -Math.abs(mDirX); + } + if (mPosY < -mAvailableY) { + mDirY = Math.abs(mDirY); + } else if (mPosY > mAvailableY) { + mDirY = -Math.abs(mDirY); + } + } + + /** + * Returns Model/View/Projection transform for the patch. + */ + public float[] getTransform(@NonNull float[] vpMatrix) { + Matrix.setIdentityM(mMatrix, 0); + mMatrix[12] = mPosX; + mMatrix[13] = mPosY; + Matrix.multiplyMM(mMatrix, 0, vpMatrix, 0, mMatrix, 0); + return mMatrix; + } +}
\ No newline at end of file diff --git a/tests/GamePerformance/src/android/gameperformance/RenderPatchOpenGLTest.java b/tests/GamePerformance/src/android/gameperformance/RenderPatchOpenGLTest.java new file mode 100644 index 000000000000..7492cc034234 --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/RenderPatchOpenGLTest.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.gameperformance; + +import java.util.List; + +import javax.microedition.khronos.opengles.GL; + +import android.annotation.NonNull; +import android.content.Context; +import android.opengl.GLES20; +import android.opengl.Matrix; + +/** + * Base class for all OpenGL based tests that use RenderPatch as a base. + */ +public abstract class RenderPatchOpenGLTest extends OpenGLTest { + private final float[] COLOR = new float[] { 1.0f, 1.0f, 1.0f, 1.0f }; + + private final String VERTEX_SHADER = + "uniform mat4 uMVPMatrix;" + + "attribute vec4 vPosition;" + + "attribute vec2 vTexture;" + + "varying vec2 vTex;" + + "void main() {" + + " vTex = vTexture;" + + " gl_Position = uMVPMatrix * vPosition;" + + "}"; + + private final String FRAGMENT_SHADER = + "precision mediump float;" + + "uniform sampler2D uTexture;" + + "uniform vec4 uColor;" + + "varying vec2 vTex;" + + "void main() {" + + " vec4 color = texture2D(uTexture, vTex);" + + " gl_FragColor = uColor * color;" + + "}"; + + private List<RenderPatchAnimation> mRenderPatches; + + private int mProgram = -1; + private int mMVPMatrixHandle; + private int mTextureHandle; + private int mPositionHandle; + private int mColorHandle; + private int mTextureCoordHandle; + + private final float[] mVPMatrix = new float[16]; + + public RenderPatchOpenGLTest(@NonNull GamePerformanceActivity activity) { + super(activity); + } + + protected void setRenderPatches(@NonNull List<RenderPatchAnimation> renderPatches) { + mRenderPatches = renderPatches; + } + + private void ensureInited() { + if (mProgram >= 0) { + return; + } + + mProgram = OpenGLUtils.createProgram(VERTEX_SHADER, FRAGMENT_SHADER); + + // get handle to fragment shader's uColor member + GLES20.glUseProgram(mProgram); + OpenGLUtils.checkGlError("useProgram"); + + // get handle to shape's transformation matrix + mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); + OpenGLUtils.checkGlError("get uMVPMatrix"); + + mTextureHandle = GLES20.glGetUniformLocation(mProgram, "uTexture"); + OpenGLUtils.checkGlError("uTexture"); + // get handle to vertex shader's vPosition member + mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition"); + OpenGLUtils.checkGlError("vPosition"); + mTextureCoordHandle = GLES20.glGetAttribLocation(mProgram, "vTexture"); + OpenGLUtils.checkGlError("vTexture"); + mColorHandle = GLES20.glGetUniformLocation(mProgram, "uColor"); + OpenGLUtils.checkGlError("uColor"); + + mTextureHandle = OpenGLUtils.createTexture(getContext(), R.drawable.logo); + + final float[] projectionMatrix = new float[16]; + final float[] viewMatrix = new float[16]; + + final float ratio = getView().getRenderRatio(); + Matrix.orthoM(projectionMatrix, 0, -ratio, ratio, -1, 1, -1, 1); + Matrix.setLookAtM(viewMatrix, 0, 0, 0, -0.5f, 0f, 0f, 0f, 0f, 1.0f, 0.0f); + Matrix.multiplyMM(mVPMatrix, 0, projectionMatrix, 0, viewMatrix, 0); + } + + /** + * Returns global color for patch. + */ + public float[] getColor() { + return COLOR; + } + + /** + * Extra setup for particular tests. + */ + public void onBeforeDraw(GL gl) { + } + + @Override + public void draw(GL gl) { + ensureInited(); + + GLES20.glUseProgram(mProgram); + OpenGLUtils.checkGlError("useProgram"); + + GLES20.glDisable(GLES20.GL_BLEND); + OpenGLUtils.checkGlError("disableBlend"); + + GLES20.glEnableVertexAttribArray(mPositionHandle); + OpenGLUtils.checkGlError("enableVertexAttributes"); + + GLES20.glEnableVertexAttribArray(mTextureCoordHandle); + OpenGLUtils.checkGlError("enableTexturesAttributes"); + + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureHandle); + OpenGLUtils.checkGlError("setTexture"); + + GLES20.glUniform4fv(mColorHandle, 1, getColor(), 0); + OpenGLUtils.checkGlError("setColor"); + + onBeforeDraw(gl); + + for (final RenderPatchAnimation renderPatchAnimation : mRenderPatches) { + + renderPatchAnimation.update(0.01f); + GLES20.glUniformMatrix4fv(mMVPMatrixHandle, + 1, + false, + renderPatchAnimation.getTransform(mVPMatrix), + 0); + OpenGLUtils.checkGlError("setTransform"); + + GLES20.glVertexAttribPointer( + mPositionHandle, + RenderPatch.VERTEX_COORD_COUNT, + GLES20.GL_FLOAT, + false /* normalized */, + RenderPatch.VERTEX_STRIDE, + renderPatchAnimation.getRenderPatch().getVertexBuffer()); + OpenGLUtils.checkGlError("setVertexAttribute"); + + GLES20.glVertexAttribPointer( + mTextureCoordHandle, + RenderPatch.TEXTURE_COORD_COUNT, + GLES20.GL_FLOAT, + false /* normalized */, + RenderPatch.TEXTURE_STRIDE, + renderPatchAnimation.getRenderPatch().getTextureBuffer()); + OpenGLUtils.checkGlError("setTextureAttribute"); + + // Draw the patch. + final int indicesCount = + renderPatchAnimation.getRenderPatch().getIndexBuffer().capacity() / + RenderPatch.SHORT_SIZE; + GLES20.glDrawElements( + GLES20.GL_TRIANGLES, + indicesCount, + GLES20.GL_UNSIGNED_SHORT, + renderPatchAnimation.getRenderPatch().getIndexBuffer()); + OpenGLUtils.checkGlError("drawPatch"); + } + + GLES20.glDisableVertexAttribArray(mPositionHandle); + GLES20.glDisableVertexAttribArray(mTextureCoordHandle); + } +}
\ No newline at end of file diff --git a/tests/GamePerformance/src/android/gameperformance/TriangleCountOpenGLTest.java b/tests/GamePerformance/src/android/gameperformance/TriangleCountOpenGLTest.java new file mode 100644 index 000000000000..593f37bf9128 --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/TriangleCountOpenGLTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.gameperformance; + +import java.util.ArrayList; +import java.util.List; + +import android.annotation.NonNull; + +/** + * Test that measures maximum amount of triangles can be rasterized keeping FPS close to the device + * refresh rate. It is has very few devices call and each call contains big amount of triangles. + * Total filling area is around one screen. + */ +public class TriangleCountOpenGLTest extends RenderPatchOpenGLTest { + // Based on index buffer of short values. + private final static int MAX_TRIANGLES_IN_PATCH = 32000; + + public TriangleCountOpenGLTest(@NonNull GamePerformanceActivity activity) { + super(activity); + } + + @Override + public String getName() { + return "triangle_count"; + } + + @Override + public String getUnitName() { + return "ktriangles"; + } + + @Override + public double getUnitScale() { + return 2.0; + } + + @Override + public void initUnits(double trianlgeCountD) { + final int triangleCount = (int)Math.round(trianlgeCountD * 1000.0); + final List<RenderPatchAnimation> renderPatches = new ArrayList<>(); + final int patchCount = + (triangleCount + MAX_TRIANGLES_IN_PATCH - 1) / MAX_TRIANGLES_IN_PATCH; + final int patchTriangleCount = triangleCount / patchCount; + for (int i = 0; i < patchCount; ++i) { + final RenderPatch renderPatch = new RenderPatch(patchTriangleCount, + 0.5f /* dimension */, + RenderPatch.TESSELLATION_TO_CENTER); + renderPatches.add(new RenderPatchAnimation(renderPatch, getView().getRenderRatio())); + } + setRenderPatches(renderPatches); + } +}
\ No newline at end of file diff --git a/tests/HwAccelerationTest/res/drawable-hdpi/sunset1.jpg b/tests/HwAccelerationTest/res/drawable-hdpi/sunset1.jpg Binary files differindex 92851f3df924..086c05542836 100644 --- a/tests/HwAccelerationTest/res/drawable-hdpi/sunset1.jpg +++ b/tests/HwAccelerationTest/res/drawable-hdpi/sunset1.jpg diff --git a/tests/HwAccelerationTest/res/drawable-nodpi/very_large_photo.jpg b/tests/HwAccelerationTest/res/drawable-nodpi/very_large_photo.jpg Binary files differindex 7f047b1098e7..6e1a866dfb00 100644 --- a/tests/HwAccelerationTest/res/drawable-nodpi/very_large_photo.jpg +++ b/tests/HwAccelerationTest/res/drawable-nodpi/very_large_photo.jpg diff --git a/tests/HwAccelerationTest/res/drawable/sunset1.jpg b/tests/HwAccelerationTest/res/drawable/sunset1.jpg Binary files differindex 92851f3df924..3b4e056b70d0 100644 --- a/tests/HwAccelerationTest/res/drawable/sunset1.jpg +++ b/tests/HwAccelerationTest/res/drawable/sunset1.jpg diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/AlphaLayersActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/AlphaLayersActivity.java index 1a68a93eed19..37661828da22 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/AlphaLayersActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/AlphaLayersActivity.java @@ -50,7 +50,7 @@ public class AlphaLayersActivity extends Activity { setContentView(container); } - + @SuppressWarnings({"UnusedDeclaration"}) static int dipToPx(Context c, int dip) { return (int) (c.getResources().getDisplayMetrics().density * dip + 0.5f); @@ -86,30 +86,24 @@ public class AlphaLayersActivity extends Activity { canvas.save(); canvas.clipRect(20.0f, 0.0f, 40.0f, 20.0f); Log.d(LOG_TAG, "clipRect = " + canvas.getClipBounds()); - Log.d(LOG_TAG, "rejected = " + canvas.quickReject(100.0f, 100.0f, 110.0f, 110.0f, - Canvas.EdgeType.BW)); - Log.d(LOG_TAG, "rejected = " + canvas.quickReject(25.0f, 5.0f, 30.0f, 10.0f, - Canvas.EdgeType.BW)); + Log.d(LOG_TAG, "rejected = " + canvas.quickReject(100.0f, 100.0f, 110.0f, 110.0f)); + Log.d(LOG_TAG, "rejected = " + canvas.quickReject(25.0f, 5.0f, 30.0f, 10.0f)); canvas.restore(); - + canvas.save(); canvas.scale(2.0f, 2.0f); canvas.clipRect(20.0f, 0.0f, 40.0f, 20.0f); Log.d(LOG_TAG, "clipRect = " + canvas.getClipBounds()); - Log.d(LOG_TAG, "rejected = " + canvas.quickReject(50.0f, 50.0f, 60.0f, 60.0f, - Canvas.EdgeType.BW)); - Log.d(LOG_TAG, "rejected = " + canvas.quickReject(25.0f, 5.0f, 30.0f, 10.0f, - Canvas.EdgeType.BW)); + Log.d(LOG_TAG, "rejected = " + canvas.quickReject(50.0f, 50.0f, 60.0f, 60.0f)); + Log.d(LOG_TAG, "rejected = " + canvas.quickReject(25.0f, 5.0f, 30.0f, 10.0f)); canvas.restore(); canvas.save(); canvas.translate(20.0f, 20.0f); canvas.clipRect(20.0f, 0.0f, 40.0f, 20.0f); Log.d(LOG_TAG, "clipRect = " + canvas.getClipBounds()); - Log.d(LOG_TAG, "rejected = " + canvas.quickReject(80.0f, 80.0f, 90.0f, 90.0f, - Canvas.EdgeType.BW)); - Log.d(LOG_TAG, "rejected = " + canvas.quickReject(25.0f, 5.0f, 30.0f, 10.0f, - Canvas.EdgeType.BW)); + Log.d(LOG_TAG, "rejected = " + canvas.quickReject(80.0f, 80.0f, 90.0f, 90.0f)); + Log.d(LOG_TAG, "rejected = " + canvas.quickReject(25.0f, 5.0f, 30.0f, 10.0f)); canvas.restore(); canvas.save(); diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java index 0787d823756c..51bae3af3e9c 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java @@ -29,9 +29,13 @@ import android.graphics.LightingColorFilter; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; +import android.graphics.RuntimeShader; import android.os.Bundle; import android.view.View; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + @SuppressWarnings({"UnusedDeclaration"}) public class ColorFiltersMutateActivity extends Activity { @Override @@ -47,12 +51,21 @@ public class ColorFiltersMutateActivity extends Activity { private final Paint mColorMatrixPaint; private final Paint mLightingPaint; private final Paint mBlendPaint; + private final Paint mShaderPaint; private float mSaturation = 0.0f; private int mLightAdd = 0; private int mLightMul = 0; private int mPorterDuffColor = 0; + static final String sSkSL = + "uniform float param1;\n" + + "void main(float x, float y, inout half4 color) {\n" + + "color = half4(color.r, half(param1), color.b, 1.0);\n" + + "}\n"; + + private byte[] mUniforms = new byte[4]; + BitmapsView(Context c) { super(c); @@ -70,6 +83,10 @@ public class ColorFiltersMutateActivity extends Activity { mBlendPaint = new Paint(); mBlendPaint.setColorFilter(new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_OVER)); + mShaderPaint = new Paint(); + mShaderPaint.setShader(new RuntimeShader(sSkSL, mUniforms, true)); + setShaderParam1(0.0f); + ObjectAnimator sat = ObjectAnimator.ofFloat(this, "saturation", 1.0f); sat.setDuration(1000); sat.setRepeatCount(ObjectAnimator.INFINITE); @@ -96,6 +113,12 @@ public class ColorFiltersMutateActivity extends Activity { color.setRepeatCount(ObjectAnimator.INFINITE); color.setRepeatMode(ObjectAnimator.REVERSE); color.start(); + + ObjectAnimator shaderUniform = ObjectAnimator.ofFloat(this, "shaderParam1", 1.0f); + shaderUniform.setDuration(1000); + shaderUniform.setRepeatCount(ObjectAnimator.INFINITE); + shaderUniform.setRepeatMode(ObjectAnimator.REVERSE); + shaderUniform.start(); } public int getPorterDuffColor() { @@ -148,6 +171,23 @@ public class ColorFiltersMutateActivity extends Activity { return mSaturation; } + public void setShaderParam1(float value) { + RuntimeShader shader = (RuntimeShader) mShaderPaint.getShader(); + ByteBuffer buffer = ByteBuffer.wrap(mUniforms); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putFloat(value); + shader.updateUniforms(mUniforms); + invalidate(); + } + + // If either valueFrom or valueTo is null, then a getter function will also be derived + // and called by the animator class. + public float getShaderParam1() { + ByteBuffer buffer = ByteBuffer.wrap(mUniforms); + buffer.order(ByteOrder.LITTLE_ENDIAN); + return buffer.getFloat(); + } + @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); @@ -163,6 +203,10 @@ public class ColorFiltersMutateActivity extends Activity { canvas.translate(0.0f, 50.0f + mBitmap1.getHeight()); canvas.drawBitmap(mBitmap1, 0.0f, 0.0f, mBlendPaint); + + canvas.translate(0.0f, 50.0f + mBitmap1.getHeight()); + canvas.drawRect(0.0f, 0.0f, mBitmap1.getWidth(), mBitmap1.getHeight(), + mShaderPaint); canvas.restore(); canvas.save(); @@ -174,6 +218,10 @@ public class ColorFiltersMutateActivity extends Activity { canvas.translate(0.0f, 50.0f + mBitmap2.getHeight()); canvas.drawBitmap(mBitmap2, 0.0f, 0.0f, mBlendPaint); + + canvas.translate(0.0f, 50.0f + mBitmap2.getHeight()); + canvas.drawRoundRect(0.0f, 0.0f, mBitmap2.getWidth(), mBitmap2.getHeight(), 20, 20, + mShaderPaint); canvas.restore(); } } diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java index 0f4c66817a05..6d8c43c00acf 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java @@ -122,7 +122,7 @@ public class TextureViewActivity extends Activity implements TextureView.Surface if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) break; } - int rotation = getWindowManager().getDefaultDisplay().getRotation(); + int rotation = getDisplay().getRotation(); int degrees = 0; switch (rotation) { diff --git a/tests/Internal/src/android/app/WallpaperColorsTest.java b/tests/Internal/src/android/app/WallpaperColorsTest.java index 65ff6eb1ba04..e9bac717daa1 100644 --- a/tests/Internal/src/android/app/WallpaperColorsTest.java +++ b/tests/Internal/src/android/app/WallpaperColorsTest.java @@ -87,6 +87,14 @@ public class WallpaperColorsTest { + "HINT_FROM_BITMAP.", fromBitmap); } + @Test + public void darkMainColorSupportsDarkTheme() { + final Color color = Color.valueOf(Color.BLACK); + WallpaperColors colors = new WallpaperColors(color, null, null); + Assert.assertTrue("Dark theme should be supported by dark main colors.", + (colors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0); + } + /** * WallpaperColors should not recycle bitmaps that it didn't create. */ diff --git a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java index 592aa3ac4a6b..153ca79e346b 100644 --- a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java +++ b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java @@ -58,4 +58,31 @@ public class WallpaperServiceTest { ambientModeChangedCount[0], 2); } + @Test + public void testDeliversZoomChanged() { + int[] zoomChangedCount = {0}; + WallpaperService service = new WallpaperService() { + @Override + public Engine onCreateEngine() { + return new Engine() { + @Override + public void onZoomChanged(float zoom) { + super.onZoomChanged(zoom); + zoomChangedCount[0]++; + } + }; + } + }; + WallpaperService.Engine engine = service.onCreateEngine(); + engine.setCreated(true); + + engine.setZoom(.5f); + assertEquals("engine scale was not updated", .5f, engine.getZoom(), .001f); + assertEquals("onZoomChanged should have been called", 1, zoomChangedCount[0]); + + engine.setZoom(0); + assertEquals("engine scale was not updated", 0, engine.getZoom(), .001f); + assertEquals("onAmbientModeChanged should have been called", 2, zoomChangedCount[0]); + } + } diff --git a/tests/JobSchedulerPerfTests/Android.bp b/tests/JobSchedulerPerfTests/Android.bp new file mode 100644 index 000000000000..2ae8c33b60a7 --- /dev/null +++ b/tests/JobSchedulerPerfTests/Android.bp @@ -0,0 +1,26 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_test { + name: "JobSchedulerPerfTests", + srcs: ["src/**/*.java"], + static_libs: [ + "androidx.test.rules", + "apct-perftests-utils", + "services", + "service-jobscheduler", + ], + platform_apis: true, + certificate: "platform", +} diff --git a/tests/JobSchedulerPerfTests/AndroidManifest.xml b/tests/JobSchedulerPerfTests/AndroidManifest.xml new file mode 100644 index 000000000000..39e751ca2a0c --- /dev/null +++ b/tests/JobSchedulerPerfTests/AndroidManifest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.perftests.job"> + <uses-sdk + android:minSdkVersion="21" /> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.perftests.job"/> +</manifest> diff --git a/tests/JobSchedulerPerfTests/AndroidTest.xml b/tests/JobSchedulerPerfTests/AndroidTest.xml new file mode 100644 index 000000000000..ca4b6c83f788 --- /dev/null +++ b/tests/JobSchedulerPerfTests/AndroidTest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs JobScheduler Performance Tests"> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="JobSchedulerPerfTests.apk"/> + <option name="cleanup-apks" value="true"/> + </target_preparer> + + <option name="test-suite-tag" value="apct"/> + <option name="test-tag" value="JobSchedulerPerfTests"/> + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.frameworks.perftests.job"/> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/> + </test> +</configuration> diff --git a/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java b/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java new file mode 100644 index 000000000000..e956be339bc4 --- /dev/null +++ b/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.frameworks.perftests.job; + + +import android.app.job.JobInfo; +import android.content.ComponentName; +import android.content.Context; +import android.os.SystemClock; +import android.perftests.utils.ManualBenchmarkState; +import android.perftests.utils.PerfManualStatusReporter; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.LargeTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.job.JobStore; +import com.android.server.job.JobStore.JobSet; +import com.android.server.job.controllers.JobStatus; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class JobStorePerfTests { + private static final String SOURCE_PACKAGE = "com.android.frameworks.perftests.job"; + private static final int SOURCE_USER_ID = 0; + private static final int CALLING_UID = 10079; + + private static Context sContext; + private static File sTestDir; + private static JobStore sJobStore; + + private static List<JobStatus> sFewJobs = new ArrayList<>(); + private static List<JobStatus> sManyJobs = new ArrayList<>(); + + @Rule + public PerfManualStatusReporter mPerfManualStatusReporter = new PerfManualStatusReporter(); + + @BeforeClass + public static void setUpOnce() { + sContext = InstrumentationRegistry.getTargetContext(); + sTestDir = new File(sContext.getFilesDir(), "JobStorePerfTests"); + sJobStore = JobStore.initAndGetForTesting(sContext, sTestDir); + + for (int i = 0; i < 50; i++) { + sFewJobs.add(createJobStatus("fewJobs", i)); + } + for (int i = 0; i < 500; i++) { + sManyJobs.add(createJobStatus("manyJobs", i)); + } + } + + @AfterClass + public static void tearDownOnce() { + sTestDir.deleteOnExit(); + } + + private void runPersistedJobWriting(List<JobStatus> jobList) { + final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState(); + + long elapsedTimeNs = 0; + while (benchmarkState.keepRunning(elapsedTimeNs)) { + sJobStore.clear(); + for (JobStatus job : jobList) { + sJobStore.add(job); + } + sJobStore.waitForWriteToCompleteForTesting(10_000); + + final long startTime = SystemClock.elapsedRealtimeNanos(); + sJobStore.writeStatusToDiskForTesting(); + final long endTime = SystemClock.elapsedRealtimeNanos(); + elapsedTimeNs = endTime - startTime; + } + } + + @Test + public void testPersistedJobWriting_fewJobs() { + runPersistedJobWriting(sFewJobs); + } + + @Test + public void testPersistedJobWriting_manyJobs() { + runPersistedJobWriting(sManyJobs); + } + + private void runPersistedJobReading(List<JobStatus> jobList, boolean rtcIsGood) { + final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState(); + + long elapsedTimeNs = 0; + while (benchmarkState.keepRunning(elapsedTimeNs)) { + sJobStore.clear(); + for (JobStatus job : jobList) { + sJobStore.add(job); + } + sJobStore.waitForWriteToCompleteForTesting(10_000); + + JobSet jobSet = new JobSet(); + + final long startTime = SystemClock.elapsedRealtimeNanos(); + sJobStore.readJobMapFromDisk(jobSet, rtcIsGood); + final long endTime = SystemClock.elapsedRealtimeNanos(); + elapsedTimeNs = endTime - startTime; + } + } + + @Test + public void testPersistedJobReading_fewJobs_goodRTC() { + runPersistedJobReading(sFewJobs, true); + } + + @Test + public void testPersistedJobReading_fewJobs_badRTC() { + runPersistedJobReading(sFewJobs, false); + } + + @Test + public void testPersistedJobReading_manyJobs_goodRTC() { + runPersistedJobReading(sManyJobs, true); + } + + @Test + public void testPersistedJobReading_manyJobs_badRTC() { + runPersistedJobReading(sManyJobs, false); + } + + private static JobStatus createJobStatus(String testTag, int jobId) { + JobInfo jobInfo = new JobInfo.Builder(jobId, + new ComponentName(sContext, "JobStorePerfTestJobService")) + .setPersisted(true) + .build(); + return JobStatus.createFromJobInfo( + jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, testTag); + } +} diff --git a/tests/ManagedProfileLifecycleStressTest/Android.bp b/tests/ManagedProfileLifecycleStressTest/Android.bp new file mode 100644 index 000000000000..639ce3cfe935 --- /dev/null +++ b/tests/ManagedProfileLifecycleStressTest/Android.bp @@ -0,0 +1,23 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +java_test_host { + name: "ManagedProfileLifecycleStressTest", + srcs: ["src/**/*.java"], + libs: ["tradefed"], + test_suites: ["device-tests"], + target_required: [ + "DummyDPC", + ], +} diff --git a/tests/RollbackTest/TestApp/res_v2/values/values.xml b/tests/ManagedProfileLifecycleStressTest/AndroidTest.xml index fd988f597f61..e7dbc5118457 100644 --- a/tests/RollbackTest/TestApp/res_v2/values/values.xml +++ b/tests/ManagedProfileLifecycleStressTest/AndroidTest.xml @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. --> - -<resources> - <integer name="app_version">2</integer> - <integer name="split_version">0</integer> -</resources> +<configuration description="Stress test for managed profile lifecycle"> + <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" > + <option name="jar" value="ManagedProfileLifecycleStressTest.jar" /> + </test> +</configuration> diff --git a/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/Android.bp b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/Android.bp new file mode 100644 index 000000000000..1f47b03d0074 --- /dev/null +++ b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/Android.bp @@ -0,0 +1,21 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_test { + name: "DummyDPC", + defaults: ["cts_defaults"], + srcs: ["src/**/*.java"], + sdk_version: "current", + test_suites: ["device-tests"], +} diff --git a/tests/RollbackTest/TestApp/ACrashingV2.xml b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/AndroidManifest.xml index 77bfd4e0f9a0..860940d4e025 100644 --- a/tests/RollbackTest/TestApp/ACrashingV2.xml +++ b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/AndroidManifest.xml @@ -15,22 +15,18 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.tests.rollback.testapp.A" - android:versionCode="2" - android:versionName="2.0" > + package="com.android.dummydpc"> - - <uses-sdk android:minSdkVersion="19" /> - - <application android:label="Rollback Test App A v2"> - <receiver android:name="com.android.tests.rollback.testapp.ProcessUserData" - android:exported="true" /> - <activity android:name="com.android.tests.rollback.testapp.CrashingMainActivity"> + <application + android:testOnly="true"> + <receiver + android:name="com.android.dummydpc.DummyDeviceAdminReceiver" + android:permission="android.permission.BIND_DEVICE_ADMIN"> + <meta-data android:name="android.app.device_admin" + android:resource="@xml/device_admin" /> <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.DEFAULT"/> - <category android:name="android.intent.category.LAUNCHER" /> + <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" /> </intent-filter> - </activity> + </receiver> </application> </manifest> diff --git a/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/res/xml/device_admin.xml b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/res/xml/device_admin.xml new file mode 100644 index 000000000000..4b3581e3e8da --- /dev/null +++ b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/res/xml/device_admin.xml @@ -0,0 +1,4 @@ +<device-admin> + <uses-policies> + </uses-policies> +</device-admin> diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/ITraceEntry.java b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/src/com/android/dummydpc/DummyDeviceAdminReceiver.java index 9525f41b46b2..92190b73b0ff 100644 --- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/ITraceEntry.java +++ b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/src/com/android/dummydpc/DummyDeviceAdminReceiver.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,15 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package com.android.dummydpc; -package com.android.server.wm.flicker; +import android.app.admin.DeviceAdminReceiver; /** - * Common interface for Layer and WindowManager trace entries. + * Empty admin to use as a managed profile owner. */ -interface ITraceEntry { - /** - * @return timestamp of current entry - */ - long getTimestamp(); +public class DummyDeviceAdminReceiver extends DeviceAdminReceiver { } + diff --git a/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java b/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java new file mode 100644 index 000000000000..026677e09bed --- /dev/null +++ b/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.stress; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A test to exercise Android Framework parts related to creating, starting, stopping, and deleting + * a managed profile as much as possible. The aim is to catch any issues in this code before it + * affects managed profile CTS tests. + */ +@RunWith(DeviceJUnit4ClassRunner.class) +public class ManagedProfileLifecycleStressTest extends BaseHostJUnit4Test { + // Stop the test once this time limit has been reached. 25 minutes used as a limit to make total + // test time less than 30 minutes, so that it can be put into presubmit. + private static final int TIME_LIMIT_MINUTES = 25; + + private static final String DUMMY_DPC_APK = "DummyDPC.apk"; + private static final String DUMMY_DPC_COMPONENT = + "com.android.dummydpc/com.android.dummydpc.DummyDeviceAdminReceiver"; + private static final Pattern CREATE_USER_OUTPUT_REGEX = + Pattern.compile("Success: created user id (\\d+)"); + + /** + * Create, start, and kill managed profiles in a loop. + */ + @Test + public void testCreateStartDelete() throws Exception { + // Disable package verifier for ADB installs. + getDevice().executeShellCommand("settings put global verifier_verify_adb_installs 0"); + int iteration = 0; + final long deadline = System.nanoTime() + TimeUnit.MINUTES.toNanos(TIME_LIMIT_MINUTES); + while (System.nanoTime() < deadline) { + iteration++; + CLog.w("Iteration N" + iteration); + final int userId = createManagedProfile(); + startUser(userId); + installPackageAsUser(DUMMY_DPC_APK, true /* grantPermissions */, userId, "-t"); + setProfileOwner(DUMMY_DPC_COMPONENT, userId); + removeUser(userId); + } + CLog.w("Completed " + iteration + " iterations."); + } + + private int createManagedProfile() throws Exception { + final String output = getDevice().executeShellCommand( + "pm create-user --profileOf 0 --managed TestProfile"); + final Matcher matcher = CREATE_USER_OUTPUT_REGEX.matcher(output.trim()); + if (!matcher.matches() || matcher.groupCount() != 1) { + fail("user creation failed, output: " + output); + } + return Integer.parseInt(matcher.group(1)); + } + + private void setProfileOwner(String componentName, int userId) throws Exception { + String command = "dpm set-profile-owner --user " + userId + " '" + componentName + "'"; + String commandOutput = getDevice().executeShellCommand(command); + assertTrue("Unexpected dpm output: " + commandOutput, commandOutput.startsWith("Success:")); + } + + private void removeUser(int userId) throws Exception { + final String output = getDevice().executeShellCommand("pm remove-user " + userId).trim(); + assertEquals("Unexpected pm output: " + output, "Success: removed user", output); + } + + private void startUser(int userId) throws Exception { + final String output = getDevice().executeShellCommand("am start-user -w " + userId).trim(); + assertEquals("Unexpected am output: " + output, "Success: user started", output); + } +} diff --git a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java index 653282d0d365..c7e5a5ea3311 100644 --- a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java +++ b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java @@ -320,8 +320,10 @@ public class MemoryUsageTest extends InstrumentationTestCase { UserHandle.USER_CURRENT); } - mAtm.startActivityAndWait(null, null, mLaunchIntent, mimeType, - null, null, 0, mLaunchIntent.getFlags(), null, null, + mAtm.startActivityAndWait(null, + getInstrumentation().getContext().getBasePackageName(), + getInstrumentation().getContext().getAttributionTag(), mLaunchIntent, + mimeType, null, null, 0, mLaunchIntent.getFlags(), null, null, UserHandle.USER_CURRENT_OR_SELF); } catch (RemoteException e) { Log.w(TAG, "Error launching app", e); diff --git a/tests/MirrorSurfaceTest/Android.bp b/tests/MirrorSurfaceTest/Android.bp new file mode 100644 index 000000000000..e359c64cc982 --- /dev/null +++ b/tests/MirrorSurfaceTest/Android.bp @@ -0,0 +1,6 @@ +android_test { + name: "MirrorSurfaceTest", + srcs: ["src/**/*.java"], + platform_apis: true, + certificate: "platform", +} diff --git a/tests/MirrorSurfaceTest/AndroidManifest.xml b/tests/MirrorSurfaceTest/AndroidManifest.xml new file mode 100644 index 000000000000..123cd0f26ff3 --- /dev/null +++ b/tests/MirrorSurfaceTest/AndroidManifest.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.google.android.test.mirrorsurface"> + <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER"/> + <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> + + <application android:label="MirrorSurfaceTest"> + <activity android:name=".MirrorSurfaceActivity" + android:label="Mirror Surface" + android:configChanges="orientation|screenSize"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + <category android:name="android.intent.category.DEFAULT"/> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/tests/MirrorSurfaceTest/res/layout/activity_mirror_surface.xml b/tests/MirrorSurfaceTest/res/layout/activity_mirror_surface.xml new file mode 100644 index 000000000000..73b509f743d1 --- /dev/null +++ b/tests/MirrorSurfaceTest/res/layout/activity_mirror_surface.xml @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:layout_marginTop="20dp" + android:orientation="horizontal"> + + <Button + android:id="@+id/mirror_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="20dp" + android:text="Mirror" + android:textSize="20dp" /> + + <Button + android:id="@+id/remove_mirror_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="20dp" + android:text="Remove Mirror" + android:textSize="20dp" /> + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="40dp" + android:layout_marginRight="40dp" + android:layout_marginTop="10dp" + android:orientation="horizontal"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="20dp" + android:text="SCALE: " /> + + <EditText + android:hint="0.5" + android:id="@+id/scale" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="numberDecimal" /> + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="40dp" + android:layout_marginRight="40dp" + android:layout_marginTop="10dp" + android:orientation="horizontal"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="20dp" + android:text="DISPLAY FRAME: " /> + + <EditText + android:hint="0, 0, 20, 20" + android:id="@+id/displayFrame" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="numberDecimal|text"/> + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="40dp" + android:layout_marginRight="40dp" + android:layout_marginTop="10dp" + android:orientation="horizontal"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="20dp" + android:text="SOURCE POSITION: " /> + + <TextView + android:id="@+id/sourcePosition" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="numberDecimal|text"/> + </LinearLayout> +</LinearLayout> diff --git a/tests/MirrorSurfaceTest/res/layout/move_view.xml b/tests/MirrorSurfaceTest/res/layout/move_view.xml new file mode 100644 index 000000000000..57077006765e --- /dev/null +++ b/tests/MirrorSurfaceTest/res/layout/move_view.xml @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="20dp" + android:gravity="center"> + + <RelativeLayout + android:id="@+id/arrows" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <ImageButton + android:id="@+id/up_arrow" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_toEndOf="@+id/right_arrow" + android:background="@android:color/holo_green_light" + android:padding="10dp" + android:src="@android:drawable/arrow_up_float" /> + + <ImageButton + android:id="@+id/down_arrow" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@+id/up_arrow" + android:layout_marginTop="80dp" + android:layout_toEndOf="@+id/right_arrow" + android:background="@android:color/holo_green_light" + android:padding="10dp" + android:src="@android:drawable/arrow_down_float" /> + + <ImageButton + android:id="@+id/right_arrow" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignTop="@+id/up_arrow" + android:layout_alignBottom="@+id/down_arrow" + android:layout_marginTop="55dp" + android:layout_marginEnd="15dp" + android:layout_marginBottom="55dp" + android:background="@android:color/holo_green_light" + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:rotation="90" + android:src="@android:drawable/arrow_down_float" /> + + <ImageButton + android:id="@+id/left_arrow" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignTop="@+id/up_arrow" + android:layout_alignBottom="@+id/down_arrow" + android:layout_marginStart="15dp" + android:layout_marginTop="55dp" + android:layout_marginBottom="55dp" + android:layout_toEndOf="@+id/down_arrow" + android:background="@android:color/holo_green_light" + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:rotation="-90" + android:src="@android:drawable/arrow_down_float" /> + </RelativeLayout> + + <RelativeLayout + + android:layout_gravity="center" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <Button + android:id="@+id/zoom_in_button" + android:layout_width="40dp" + android:layout_marginBottom="-8dp" + android:layout_height="40dp" + android:text="+" /> + + <Button + android:layout_below="@+id/zoom_in_button" + android:id="@+id/zoom_out_button" + android:layout_width="40dp" + android:layout_height="40dp" + android:text="-" /> + </RelativeLayout> +</FrameLayout>
\ No newline at end of file diff --git a/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java b/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java new file mode 100644 index 000000000000..8afe8411a790 --- /dev/null +++ b/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java @@ -0,0 +1,445 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.test.mirrorsurface; + +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + +import android.app.Activity; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.Handler; +import android.os.RemoteException; +import android.view.Gravity; +import android.view.IWindowManager; +import android.view.MotionEvent; +import android.view.Surface; +import android.view.SurfaceControl; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.window.WindowMetricsHelper; + +public class MirrorSurfaceActivity extends Activity implements View.OnClickListener, + View.OnLongClickListener, View.OnTouchListener { + private static final int BORDER_SIZE = 10; + private static final int DEFAULT_SCALE = 2; + private static final int DEFAULT_BORDER_COLOR = Color.argb(255, 255, 153, 0); + private static final int MOVE_FRAME_AMOUNT = 20; + + private IWindowManager mIWm; + // An instance of WindowManager that is adjusted for adding windows with type + // TYPE_APPLICATION_OVERLAY. + private WindowManager mWm; + + private SurfaceControl mSurfaceControl = new SurfaceControl(); + private SurfaceControl mBorderSc; + + private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); + private View mOverlayView; + private View mArrowOverlay; + + private Rect mWindowBounds = new Rect(); + + private EditText mScaleText; + private EditText mDisplayFrameText; + private TextView mSourcePositionText; + + private Rect mTmpRect = new Rect(); + private final Surface mTmpSurface = new Surface(); + + private boolean mHasMirror; + + private Rect mCurrFrame = new Rect(); + private float mCurrScale = DEFAULT_SCALE; + + private final Handler mHandler = new Handler(); + + private MoveMirrorRunnable mMoveMirrorRunnable = new MoveMirrorRunnable(); + private boolean mIsPressedDown = false; + + private int mDisplayId; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_mirror_surface); + mWm = createWindowContext(TYPE_APPLICATION_OVERLAY, null /* options */) + .getSystemService(WindowManager.class); + mIWm = WindowManagerGlobal.getWindowManagerService(); + + Rect windowBounds = WindowMetricsHelper.getBoundsExcludingNavigationBarAndCutout( + mWm.getCurrentWindowMetrics()); + mWindowBounds.set(0, 0, windowBounds.width(), windowBounds.height()); + + mScaleText = findViewById(R.id.scale); + mDisplayFrameText = findViewById(R.id.displayFrame); + mSourcePositionText = findViewById(R.id.sourcePosition); + + mCurrFrame.set(0, 0, mWindowBounds.width() / 2, mWindowBounds.height() / 2); + mCurrScale = DEFAULT_SCALE; + + mDisplayId = getDisplay().getDisplayId(); + updateEditTexts(); + + findViewById(R.id.mirror_button).setOnClickListener(view -> { + if (mArrowOverlay == null) { + createArrowOverlay(); + } + createOrUpdateMirror(); + }); + + findViewById(R.id.remove_mirror_button).setOnClickListener(v -> { + removeMirror(); + removeArrowOverlay(); + }); + + createMirrorOverlay(); + } + + private void updateEditTexts() { + mDisplayFrameText.setText( + String.format("%s, %s, %s, %s", mCurrFrame.left, mCurrFrame.top, mCurrFrame.right, + mCurrFrame.bottom)); + mScaleText.setText(String.valueOf(mCurrScale)); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (mOverlayView != null) { + removeMirror(); + mWm.removeView(mOverlayView); + mOverlayView = null; + } + removeArrowOverlay(); + } + + private void createArrowOverlay() { + mArrowOverlay = getLayoutInflater().inflate(R.layout.move_view, null); + WindowManager.LayoutParams arrowParams = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.RGBA_8888); + arrowParams.gravity = Gravity.RIGHT | Gravity.BOTTOM; + mWm.addView(mArrowOverlay, arrowParams); + + View leftArrow = mArrowOverlay.findViewById(R.id.left_arrow); + View topArrow = mArrowOverlay.findViewById(R.id.up_arrow); + View rightArrow = mArrowOverlay.findViewById(R.id.right_arrow); + View bottomArrow = mArrowOverlay.findViewById(R.id.down_arrow); + + leftArrow.setOnClickListener(this); + topArrow.setOnClickListener(this); + rightArrow.setOnClickListener(this); + bottomArrow.setOnClickListener(this); + + leftArrow.setOnLongClickListener(this); + topArrow.setOnLongClickListener(this); + rightArrow.setOnLongClickListener(this); + bottomArrow.setOnLongClickListener(this); + + leftArrow.setOnTouchListener(this); + topArrow.setOnTouchListener(this); + rightArrow.setOnTouchListener(this); + bottomArrow.setOnTouchListener(this); + + mArrowOverlay.findViewById(R.id.zoom_in_button).setOnClickListener(v -> { + if (mCurrScale <= 1) { + mCurrScale *= 2; + } else { + mCurrScale += 0.5; + } + + updateMirror(mCurrFrame, mCurrScale); + }); + mArrowOverlay.findViewById(R.id.zoom_out_button).setOnClickListener(v -> { + if (mCurrScale <= 1) { + mCurrScale /= 2; + } else { + mCurrScale -= 0.5; + } + + updateMirror(mCurrFrame, mCurrScale); + }); + } + + private void removeArrowOverlay() { + if (mArrowOverlay != null) { + mWm.removeView(mArrowOverlay); + mArrowOverlay = null; + } + } + + private void createMirrorOverlay() { + mOverlayView = new LinearLayout(this); + WindowManager.LayoutParams params = new WindowManager.LayoutParams(mWindowBounds.width(), + mWindowBounds.height(), + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.RGBA_8888); + params.gravity = Gravity.LEFT | Gravity.TOP; + params.setTitle("Mirror Overlay"); + mOverlayView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + + mWm.addView(mOverlayView, params); + + } + + private void removeMirror() { + if (mSurfaceControl.isValid()) { + mTransaction.remove(mSurfaceControl).apply(); + } + mHasMirror = false; + } + + private void createOrUpdateMirror() { + if (mHasMirror) { + updateMirror(getDisplayFrame(), getScale()); + } else { + createMirror(getDisplayFrame(), getScale()); + } + + } + + private Rect getDisplayFrame() { + mTmpRect.setEmpty(); + String[] frameVals = mDisplayFrameText.getText().toString().split("\\s*,\\s*"); + if (frameVals.length != 4) { + return mTmpRect; + } + + try { + mTmpRect.set(Integer.parseInt(frameVals[0]), Integer.parseInt(frameVals[1]), + Integer.parseInt(frameVals[2]), Integer.parseInt(frameVals[3])); + } catch (Exception e) { + mTmpRect.setEmpty(); + } + + return mTmpRect; + } + + private float getScale() { + try { + return Float.parseFloat(mScaleText.getText().toString()); + } catch (Exception e) { + return -1; + } + } + + private void createMirror(Rect displayFrame, float scale) { + boolean success = false; + try { + success = mIWm.mirrorDisplay(mDisplayId, mSurfaceControl); + } catch (RemoteException e) { + } + + if (!success) { + return; + } + + if (!mSurfaceControl.isValid()) { + return; + } + + mHasMirror = true; + + mBorderSc = new SurfaceControl.Builder() + .setName("Mirror Border") + .setBufferSize(1, 1) + .setFormat(PixelFormat.TRANSLUCENT) + .build(); + + updateMirror(displayFrame, scale); + + mTransaction + .show(mSurfaceControl) + .reparent(mSurfaceControl, mOverlayView.getViewRootImpl().getSurfaceControl()) + .setLayer(mBorderSc, 1) + .show(mBorderSc) + .reparent(mBorderSc, mSurfaceControl) + .apply(); + } + + private void updateMirror(Rect displayFrame, float scale) { + if (displayFrame.isEmpty()) { + Rect bounds = mWindowBounds; + int defaultCropW = Math.round(bounds.width() / 2); + int defaultCropH = Math.round(bounds.height() / 2); + displayFrame.set(0, 0, defaultCropW, defaultCropH); + } + + if (scale <= 0) { + scale = DEFAULT_SCALE; + } + + mCurrFrame.set(displayFrame); + mCurrScale = scale; + + int width = (int) Math.ceil(displayFrame.width() / scale); + int height = (int) Math.ceil(displayFrame.height() / scale); + + Rect sourceBounds = getSourceBounds(displayFrame, scale); + + mTransaction.setGeometry(mSurfaceControl, sourceBounds, displayFrame, Surface.ROTATION_0) + .setPosition(mBorderSc, sourceBounds.left, sourceBounds.top) + .setBufferSize(mBorderSc, width, height) + .apply(); + + drawBorder(mBorderSc, width, height, (int) Math.ceil(BORDER_SIZE / scale)); + + mSourcePositionText.setText(sourceBounds.left + ", " + sourceBounds.top); + mDisplayFrameText.setText( + String.format("%s, %s, %s, %s", mCurrFrame.left, mCurrFrame.top, mCurrFrame.right, + mCurrFrame.bottom)); + mScaleText.setText(String.valueOf(mCurrScale)); + } + + private void drawBorder(SurfaceControl borderSc, int width, int height, int borderSize) { + mTmpSurface.copyFrom(borderSc); + + Canvas c = null; + try { + c = mTmpSurface.lockCanvas(null); + } catch (IllegalArgumentException | Surface.OutOfResourcesException e) { + } + if (c == null) { + return; + } + + // Top + c.save(); + c.clipRect(new Rect(0, 0, width, borderSize)); + c.drawColor(DEFAULT_BORDER_COLOR); + c.restore(); + // Left + c.save(); + c.clipRect(new Rect(0, 0, borderSize, height)); + c.drawColor(DEFAULT_BORDER_COLOR); + c.restore(); + // Right + c.save(); + c.clipRect(new Rect(width - borderSize, 0, width, height)); + c.drawColor(DEFAULT_BORDER_COLOR); + c.restore(); + // Bottom + c.save(); + c.clipRect(new Rect(0, height - borderSize, width, height)); + c.drawColor(DEFAULT_BORDER_COLOR); + c.restore(); + + mTmpSurface.unlockCanvasAndPost(c); + } + + @Override + public void onClick(View v) { + Point offset = findOffset(v); + moveMirrorForArrows(offset.x, offset.y); + } + + @Override + public boolean onLongClick(View v) { + mIsPressedDown = true; + Point point = findOffset(v); + mMoveMirrorRunnable.mXOffset = point.x; + mMoveMirrorRunnable.mYOffset = point.y; + mHandler.post(mMoveMirrorRunnable); + return false; + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mIsPressedDown = false; + break; + } + return false; + } + + private Point findOffset(View v) { + Point offset = new Point(0, 0); + + switch (v.getId()) { + case R.id.up_arrow: + offset.y = -MOVE_FRAME_AMOUNT; + break; + case R.id.down_arrow: + offset.y = MOVE_FRAME_AMOUNT; + break; + case R.id.right_arrow: + offset.x = -MOVE_FRAME_AMOUNT; + break; + case R.id.left_arrow: + offset.x = MOVE_FRAME_AMOUNT; + break; + } + + return offset; + } + + private void moveMirrorForArrows(int xOffset, int yOffset) { + mCurrFrame.offset(xOffset, yOffset); + + updateMirror(mCurrFrame, mCurrScale); + } + + /** + * Calculates the desired source bounds. This will be the area under from the center of the + * displayFrame, factoring in scale. + */ + private Rect getSourceBounds(Rect displayFrame, float scale) { + int halfWidth = displayFrame.width() / 2; + int halfHeight = displayFrame.height() / 2; + int left = displayFrame.left + (halfWidth - (int) (halfWidth / scale)); + int right = displayFrame.right - (halfWidth - (int) (halfWidth / scale)); + int top = displayFrame.top + (halfHeight - (int) (halfHeight / scale)); + int bottom = displayFrame.bottom - (halfHeight - (int) (halfHeight / scale)); + return new Rect(left, top, right, bottom); + } + + class MoveMirrorRunnable implements Runnable { + int mXOffset = 0; + int mYOffset = 0; + + @Override + public void run() { + if (mIsPressedDown) { + moveMirrorForArrows(mXOffset, mYOffset); + mHandler.postDelayed(mMoveMirrorRunnable, 150); + } + } + } +} diff --git a/tests/NullHomeTest/Android.bp b/tests/NullHomeTest/Android.bp new file mode 100644 index 000000000000..99248bfe1da1 --- /dev/null +++ b/tests/NullHomeTest/Android.bp @@ -0,0 +1,22 @@ +// Copyright 2020 Google Inc. All Rights Reserved. +// +// 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. + +android_test { + name: "NullHomeTest", + srcs: ["src/**/*.java"], + certificate: "platform", + platform_apis: true, + static_libs: ["android-support-test"], + test_suites: ["device-tests"], +} diff --git a/tests/NullHomeTest/AndroidManifest.xml b/tests/NullHomeTest/AndroidManifest.xml new file mode 100644 index 000000000000..dc6402e03b5a --- /dev/null +++ b/tests/NullHomeTest/AndroidManifest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.test.nullhome" + android:sharedUserId="android.uid.system" > + + <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="21" /> + + <instrumentation + android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.test.nullhome" + android:label="Check if no null Home exists/is enabled" /> + + <application android:label="Null Home Test"> + <uses-library android:name="android.test.runner" /> + </application> +</manifest> diff --git a/tests/NullHomeTest/src/com/android/test/nullhome/NullHomeTest.java b/tests/NullHomeTest/src/com/android/test/nullhome/NullHomeTest.java new file mode 100644 index 000000000000..1d77cdc51187 --- /dev/null +++ b/tests/NullHomeTest/src/com/android/test/nullhome/NullHomeTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.test.nullhome; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.support.test.InstrumentationRegistry; +import android.util.Log; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayList; +import java.util.List; + +/* + * Check if NullHome/SystemUserHome activity does not exist/is disabled. + * + * SystemUserHome is only enabled in bootable CSI (csi_x86, csi_arm64) + * products and should not be enabled in other products. + * + * Shell's NullHome is empty and caused issues in sevaral manual GUI tests + * that try to select/use it, and should be removed. + * + * Settings' FallbackHome is fine because it's specially handled by Settings. + * + */ + +@RunWith(JUnit4.class) +public class NullHomeTest { + private static final String TAG = "NullHomeTest"; + private Context mContext; + private PackageManager mPm; + + @Before + public void before() { + Log.d(TAG, "beforeClass()"); + mContext = InstrumentationRegistry.getInstrumentation().getContext(); + mPm = mContext.getPackageManager(); + } + + @Test + public void checkNullHome() { + final List<ResolveInfo> homeActivities = new ArrayList<>(); + + mPm.getHomeActivities(homeActivities); + for (ResolveInfo activity : homeActivities) { + Log.d(TAG, "Home activity: " + activity.activityInfo.packageName); + Assert.assertNotEquals(activity.activityInfo.packageName, + "com.android.internal.app.SystemUserHomeActivity"); + Assert.assertNotEquals(activity.activityInfo.packageName, + "com.android.shell"); + } + } +} diff --git a/tests/PackageWatchdog/Android.bp b/tests/PackageWatchdog/Android.bp index 88d92c4d94fe..0b75039cf69f 100644 --- a/tests/PackageWatchdog/Android.bp +++ b/tests/PackageWatchdog/Android.bp @@ -23,6 +23,7 @@ android_test { "androidx.test.rules", "services.core", "services.net", + "truth-prebuilt", ], libs: ["android.test.runner"], jni_libs: [ diff --git a/tests/PackageWatchdog/src/com/android/server/ExplicitHealthCheckServiceTest.java b/tests/PackageWatchdog/src/com/android/server/ExplicitHealthCheckServiceTest.java new file mode 100644 index 000000000000..2fbfeba47b13 --- /dev/null +++ b/tests/PackageWatchdog/src/com/android/server/ExplicitHealthCheckServiceTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; + +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteCallback; +import android.service.watchdog.ExplicitHealthCheckService; +import android.service.watchdog.IExplicitHealthCheckService; + +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; + +public class ExplicitHealthCheckServiceTest { + + private ExplicitHealthCheckService mExplicitHealthCheckService; + private static final String PACKAGE_NAME = "com.test.package"; + + @Before + public void setup() throws Exception { + mExplicitHealthCheckService = spy(ExplicitHealthCheckService.class); + } + + /** + * Test to verify that the correct information is sent in the callback when a package has + * passed an explicit health check. + */ + @Test + public void testNotifyHealthCheckPassed() throws Exception { + IBinder binder = mExplicitHealthCheckService.onBind(new Intent()); + CountDownLatch countDownLatch = new CountDownLatch(1); + RemoteCallback callback = new RemoteCallback(result -> { + assertThat(result.get(ExplicitHealthCheckService.EXTRA_HEALTH_CHECK_PASSED_PACKAGE)) + .isEqualTo(PACKAGE_NAME); + countDownLatch.countDown(); + }); + IExplicitHealthCheckService.Stub.asInterface(binder).setCallback(callback); + mExplicitHealthCheckService.notifyHealthCheckPassed(PACKAGE_NAME); + countDownLatch.await(); + } +} diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java index 31c6996217f2..ae93a81f274e 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -18,12 +18,13 @@ package com.android.server; import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; + +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; @@ -35,15 +36,18 @@ import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; -import android.net.NetworkStackClient; -import android.net.NetworkStackClient.NetworkStackHealthListener; +import android.net.ConnectivityModuleConnector; +import android.net.ConnectivityModuleConnector.ConnectivityModuleHealthListener; import android.os.Handler; +import android.os.SystemProperties; import android.os.test.TestLooper; import android.provider.DeviceConfig; import android.util.AtomicFile; import androidx.test.InstrumentationRegistry; +import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.server.PackageWatchdog.HealthCheckState; import com.android.server.PackageWatchdog.MonitoredPackage; import com.android.server.PackageWatchdog.PackageHealthObserver; import com.android.server.PackageWatchdog.PackageHealthObserverImpact; @@ -55,19 +59,20 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; +import org.mockito.stubbing.Answer; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; -// TODO: Write test without using PackageWatchdog#getPackages. Just rely on -// behavior of observers receiving crash notifications or not to determine if it's registered -// TODO: Use Truth in tests. /** * Test PackageWatchdog. */ @@ -83,14 +88,17 @@ public class PackageWatchdogTest { private static final String OBSERVER_NAME_4 = "observer4"; private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(1); private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(5); + private final TestClock mTestClock = new TestClock(); private TestLooper mTestLooper; private Context mSpyContext; @Mock - private NetworkStackClient mMockNetworkStackClient; + private ConnectivityModuleConnector mConnectivityModuleConnector; @Mock private PackageManager mMockPackageManager; @Captor - private ArgumentCaptor<NetworkStackHealthListener> mNetworkStackCallbackCaptor; + private ArgumentCaptor<ConnectivityModuleHealthListener> mConnectivityModuleCallbackCaptor; + private MockitoSession mSession; + private HashMap<String, String> mSystemSettingsMap; @Before public void setUp() throws Exception { @@ -107,85 +115,154 @@ public class PackageWatchdogTest { res.setLongVersionCode(VERSION_CODE); return res; }); + mSession = ExtendedMockito.mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .spyStatic(SystemProperties.class) + .startMocking(); + mSystemSettingsMap = new HashMap<>(); + + + // Mock SystemProperties setter and various getters + doAnswer((Answer<Void>) invocationOnMock -> { + String key = invocationOnMock.getArgument(0); + String value = invocationOnMock.getArgument(1); + + mSystemSettingsMap.put(key, value); + return null; + } + ).when(() -> SystemProperties.set(anyString(), anyString())); + + doAnswer((Answer<Integer>) invocationOnMock -> { + String key = invocationOnMock.getArgument(0); + int defaultValue = invocationOnMock.getArgument(1); + + String storedValue = mSystemSettingsMap.get(key); + return storedValue == null ? defaultValue : Integer.parseInt(storedValue); + } + ).when(() -> SystemProperties.getInt(anyString(), anyInt())); + + doAnswer((Answer<Long>) invocationOnMock -> { + String key = invocationOnMock.getArgument(0); + long defaultValue = invocationOnMock.getArgument(1); + + String storedValue = mSystemSettingsMap.get(key); + return storedValue == null ? defaultValue : Long.parseLong(storedValue); + } + ).when(() -> SystemProperties.getLong(anyString(), anyLong())); } @After public void tearDown() throws Exception { dropShellPermissions(); + mSession.finishMocking(); + } + + @Test + public void testRegistration_singleObserver() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer = new TestObserver(OBSERVER_NAME_1); + + watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + + // The failed packages should be the same as the registered ones to ensure registration is + // done successfully + assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A); } - /** - * Test registration, unregistration, package expiry and duration reduction - */ @Test - public void testRegistration() throws Exception { + public void testRegistration_multiObservers() { PackageWatchdog watchdog = createWatchdog(); TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); - TestObserver observer3 = new TestObserver(OBSERVER_NAME_3); - // Start observing for observer1 which will be unregistered watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); - // Start observing for observer2 which will expire watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION); - // Start observing for observer3 which will have expiry duration reduced - watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), LONG_DURATION); - - // Verify packages observed at start - // 1 - assertEquals(1, watchdog.getPackages(observer1).size()); - assertTrue(watchdog.getPackages(observer1).contains(APP_A)); - // 2 - assertEquals(2, watchdog.getPackages(observer2).size()); - assertTrue(watchdog.getPackages(observer2).contains(APP_A)); - assertTrue(watchdog.getPackages(observer2).contains(APP_B)); - // 3 - assertEquals(1, watchdog.getPackages(observer3).size()); - assertTrue(watchdog.getPackages(observer3).contains(APP_A)); - - // Then unregister observer1 - watchdog.unregisterHealthObserver(observer1); - - // Verify observer2 and observer3 left - // 1 - assertNull(watchdog.getPackages(observer1)); - // 2 - assertEquals(2, watchdog.getPackages(observer2).size()); - assertTrue(watchdog.getPackages(observer2).contains(APP_A)); - assertTrue(watchdog.getPackages(observer2).contains(APP_B)); - // 3 - assertEquals(1, watchdog.getPackages(observer3).size()); - assertTrue(watchdog.getPackages(observer3).contains(APP_A)); - - // Then advance time a little and run messages in Handlers so observer2 expires - Thread.sleep(SHORT_DURATION); - mTestLooper.dispatchAll(); + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE), + new VersionedPackage(APP_B, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + + // The failed packages should be the same as the registered ones to ensure registration is + // done successfully + assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A); + assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A, APP_B); + } - // Verify observer3 left with reduced expiry duration - // 1 - assertNull(watchdog.getPackages(observer1)); - // 2 - assertNull(watchdog.getPackages(observer2)); - // 3 - assertEquals(1, watchdog.getPackages(observer3).size()); - assertTrue(watchdog.getPackages(observer3).contains(APP_A)); - - // Then advance time some more and run messages in Handlers so observer3 expires - Thread.sleep(LONG_DURATION); - mTestLooper.dispatchAll(); + @Test + public void testUnregistration_singleObserver() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer = new TestObserver(OBSERVER_NAME_1); + + watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.unregisterHealthObserver(observer); + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); - // Verify observer3 expired - // 1 - assertNull(watchdog.getPackages(observer1)); - // 2 - assertNull(watchdog.getPackages(observer2)); - // 3 - assertNull(watchdog.getPackages(observer3)); + // We should have no failed packages to ensure unregistration is done successfully + assertThat(observer.mHealthCheckFailedPackages).isEmpty(); + } + + @Test + public void testUnregistration_multiObservers() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); + TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); + + watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.unregisterHealthObserver(observer2); + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + + // observer1 should receive failed packages as intended. + assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A); + // observer2 should have no failed packages to ensure unregistration is done successfully + assertThat(observer2.mHealthCheckFailedPackages).isEmpty(); + } + + @Test + public void testExpiration_singleObserver() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer = new TestObserver(OBSERVER_NAME_1); + + watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); + moveTimeForwardAndDispatch(SHORT_DURATION); + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + + // We should have no failed packages for the fatal failure is raised after expiration + assertThat(observer.mHealthCheckFailedPackages).isEmpty(); + } + + @Test + public void testExpiration_multiObservers() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); + TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); + + watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), LONG_DURATION); + moveTimeForwardAndDispatch(SHORT_DURATION); + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + + // We should have no failed packages for the fatal failure is raised after expiration + assertThat(observer1.mHealthCheckFailedPackages).isEmpty(); + // We should have failed packages since observer2 hasn't expired + assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A); } /** Observing already observed package extends the observation time. */ @Test - public void testObserveAlreadyObservedPackage() throws Exception { + public void testObserveAlreadyObservedPackage() { PackageWatchdog watchdog = createWatchdog(); TestObserver observer = new TestObserver(OBSERVER_NAME_1); @@ -193,66 +270,49 @@ public class PackageWatchdogTest { watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); // Then advance time half-way - Thread.sleep(SHORT_DURATION / 2); - mTestLooper.dispatchAll(); + moveTimeForwardAndDispatch(SHORT_DURATION / 2); // Start observing APP_A again watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); // Then advance time such that it should have expired were it not for the second observation - Thread.sleep((SHORT_DURATION / 2) + 1); - mTestLooper.dispatchAll(); + moveTimeForwardAndDispatch((SHORT_DURATION / 2) + 1); + + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); - // Verify that APP_A not expired since second observation extended the time - assertEquals(1, watchdog.getPackages(observer).size()); - assertTrue(watchdog.getPackages(observer).contains(APP_A)); + // Verify that we receive failed packages as expected for APP_A not expired + assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A); } /** * Test package observers are persisted and loaded on startup */ @Test - public void testPersistence() throws Exception { + public void testPersistence() { PackageWatchdog watchdog1 = createWatchdog(); TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION); - - // Verify 2 observers are registered and saved internally - // 1 - assertEquals(1, watchdog1.getPackages(observer1).size()); - assertTrue(watchdog1.getPackages(observer1).contains(APP_A)); - // 2 - assertEquals(2, watchdog1.getPackages(observer2).size()); - assertTrue(watchdog1.getPackages(observer2).contains(APP_A)); - assertTrue(watchdog1.getPackages(observer2).contains(APP_B)); - // Then advance time and run IO Handler so file is saved mTestLooper.dispatchAll(); - // Then start a new watchdog PackageWatchdog watchdog2 = createWatchdog(); - - // Verify the new watchdog loads observers on startup but nothing registered - assertEquals(0, watchdog2.getPackages(observer1).size()); - assertEquals(0, watchdog2.getPackages(observer2).size()); - // Verify random observer not saved returns null - assertNull(watchdog2.getPackages(new TestObserver(OBSERVER_NAME_3))); - - // Then register observer1 + // Then resume observer1 and observer2 watchdog2.registerHealthObserver(observer1); watchdog2.registerHealthObserver(observer2); - - // Verify 2 observers are registered after reload - // 1 - assertEquals(1, watchdog1.getPackages(observer1).size()); - assertTrue(watchdog1.getPackages(observer1).contains(APP_A)); - // 2 - assertEquals(2, watchdog1.getPackages(observer2).size()); - assertTrue(watchdog1.getPackages(observer2).contains(APP_A)); - assertTrue(watchdog1.getPackages(observer2).contains(APP_B)); + raiseFatalFailureAndDispatch(watchdog2, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE), + new VersionedPackage(APP_B, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + + // We should receive failed packages as expected to ensure observers are persisted and + // resumed correctly + assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A); + assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A, APP_B); } /** @@ -277,8 +337,8 @@ public class PackageWatchdogTest { mTestLooper.dispatchAll(); // Verify that observers are not notified - assertEquals(0, observer1.mFailedPackages.size()); - assertEquals(0, observer2.mFailedPackages.size()); + assertThat(observer1.mHealthCheckFailedPackages).isEmpty(); + assertThat(observer2.mHealthCheckFailedPackages).isEmpty(); } /** @@ -296,17 +356,13 @@ public class PackageWatchdogTest { watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION); // Then fail APP_C (not observed) above the threshold - for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { - watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_C, VERSION_CODE)), - PackageWatchdog.FAILURE_REASON_UNKNOWN); - } - - // Run handler so package failures are dispatched to observers - mTestLooper.dispatchAll(); + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_C, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); // Verify that observers are not notified - assertEquals(0, observer1.mFailedPackages.size()); - assertEquals(0, observer2.mFailedPackages.size()); + assertThat(observer1.mHealthCheckFailedPackages).isEmpty(); + assertThat(observer2.mHealthCheckFailedPackages).isEmpty(); } /** @@ -319,7 +375,8 @@ public class PackageWatchdogTest { long differentVersionCode = 2L; TestObserver observer = new TestObserver(OBSERVER_NAME_1) { @Override - public int onHealthCheckFailed(VersionedPackage versionedPackage) { + public int onHealthCheckFailed(VersionedPackage versionedPackage, + int failureReason) { if (versionedPackage.getVersionCode() == VERSION_CODE) { // Only rollback for specific versionCode return PackageHealthObserverImpact.USER_IMPACT_MEDIUM; @@ -331,17 +388,12 @@ public class PackageWatchdogTest { watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); // Then fail APP_A (different version) above the threshold - for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { - watchdog.onPackageFailure(Arrays.asList( - new VersionedPackage(APP_A, differentVersionCode)), - PackageWatchdog.FAILURE_REASON_UNKNOWN); - } - - // Run handler so package failures are dispatched to observers - mTestLooper.dispatchAll(); + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, differentVersionCode)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); // Verify that observers are not notified - assertEquals(0, observer.mFailedPackages.size()); + assertThat(observer.mHealthCheckFailedPackages).isEmpty(); } @@ -371,34 +423,27 @@ public class PackageWatchdogTest { SHORT_DURATION); // Then fail all apps above the threshold - for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { - watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE), - new VersionedPackage(APP_B, VERSION_CODE), - new VersionedPackage(APP_C, VERSION_CODE), - new VersionedPackage(APP_D, VERSION_CODE)), - PackageWatchdog.FAILURE_REASON_UNKNOWN); - } - - // Run handler so package failures are dispatched to observers - mTestLooper.dispatchAll(); + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE), + new VersionedPackage(APP_B, VERSION_CODE), + new VersionedPackage(APP_C, VERSION_CODE), + new VersionedPackage(APP_D, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); // Verify least impact observers are notifed of package failures - List<String> observerNonePackages = observerNone.mFailedPackages; - List<String> observerHighPackages = observerHigh.mFailedPackages; - List<String> observerMidPackages = observerMid.mFailedPackages; - List<String> observerLowPackages = observerLow.mFailedPackages; + List<String> observerNonePackages = observerNone.mMitigatedPackages; + List<String> observerHighPackages = observerHigh.mMitigatedPackages; + List<String> observerMidPackages = observerMid.mMitigatedPackages; + List<String> observerLowPackages = observerLow.mMitigatedPackages; // APP_D failure observed by only observerNone is not caught cos its impact is none - assertEquals(0, observerNonePackages.size()); + assertThat(observerNonePackages).isEmpty(); // APP_C failure is caught by observerHigh cos it's the lowest impact observer - assertEquals(1, observerHighPackages.size()); - assertEquals(APP_C, observerHighPackages.get(0)); + assertThat(observerHighPackages).containsExactly(APP_C); // APP_B failure is caught by observerMid cos it's the lowest impact observer - assertEquals(1, observerMidPackages.size()); - assertEquals(APP_B, observerMidPackages.get(0)); + assertThat(observerMidPackages).containsExactly(APP_B); // APP_A failure is caught by observerLow cos it's the lowest impact observer - assertEquals(1, observerLowPackages.size()); - assertEquals(APP_A, observerLowPackages.get(0)); + assertThat(observerLowPackages).containsExactly(APP_A); } /** @@ -425,70 +470,55 @@ public class PackageWatchdogTest { watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION); // Then fail APP_A above the threshold - for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { - watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), - PackageWatchdog.FAILURE_REASON_UNKNOWN); - } - // Run handler so package failures are dispatched to observers - mTestLooper.dispatchAll(); + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); // Verify only observerFirst is notifed - assertEquals(1, observerFirst.mFailedPackages.size()); - assertEquals(APP_A, observerFirst.mFailedPackages.get(0)); - assertEquals(0, observerSecond.mFailedPackages.size()); + assertThat(observerFirst.mMitigatedPackages).containsExactly(APP_A); + assertThat(observerSecond.mMitigatedPackages).isEmpty(); // After observerFirst handles failure, next action it has is high impact observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_HIGH; - observerFirst.mFailedPackages.clear(); - observerSecond.mFailedPackages.clear(); + observerFirst.mMitigatedPackages.clear(); + observerSecond.mMitigatedPackages.clear(); // Then fail APP_A again above the threshold - for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { - watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), - PackageWatchdog.FAILURE_REASON_UNKNOWN); - } - // Run handler so package failures are dispatched to observers - mTestLooper.dispatchAll(); + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); // Verify only observerSecond is notifed cos it has least impact - assertEquals(1, observerSecond.mFailedPackages.size()); - assertEquals(APP_A, observerSecond.mFailedPackages.get(0)); - assertEquals(0, observerFirst.mFailedPackages.size()); + assertThat(observerSecond.mMitigatedPackages).containsExactly(APP_A); + assertThat(observerFirst.mMitigatedPackages).isEmpty(); // After observerSecond handles failure, it has no further actions observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE; - observerFirst.mFailedPackages.clear(); - observerSecond.mFailedPackages.clear(); + observerFirst.mMitigatedPackages.clear(); + observerSecond.mMitigatedPackages.clear(); // Then fail APP_A again above the threshold - for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { - watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), - PackageWatchdog.FAILURE_REASON_UNKNOWN); - } - // Run handler so package failures are dispatched to observers - mTestLooper.dispatchAll(); + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); // Verify only observerFirst is notifed cos it has the only action - assertEquals(1, observerFirst.mFailedPackages.size()); - assertEquals(APP_A, observerFirst.mFailedPackages.get(0)); - assertEquals(0, observerSecond.mFailedPackages.size()); + assertThat(observerFirst.mMitigatedPackages).containsExactly(APP_A); + assertThat(observerSecond.mMitigatedPackages).isEmpty(); // After observerFirst handles failure, it too has no further actions observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE; - observerFirst.mFailedPackages.clear(); - observerSecond.mFailedPackages.clear(); + observerFirst.mMitigatedPackages.clear(); + observerSecond.mMitigatedPackages.clear(); // Then fail APP_A again above the threshold - for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { - watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), - PackageWatchdog.FAILURE_REASON_UNKNOWN); - } - // Run handler so package failures are dispatched to observers - mTestLooper.dispatchAll(); + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); // Verify no observer is notified cos no actions left - assertEquals(0, observerFirst.mFailedPackages.size()); - assertEquals(0, observerSecond.mFailedPackages.size()); + assertThat(observerFirst.mMitigatedPackages).isEmpty(); + assertThat(observerSecond.mMitigatedPackages).isEmpty(); } /** @@ -507,18 +537,13 @@ public class PackageWatchdogTest { watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); // Then fail APP_A above the threshold - for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { - watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), - PackageWatchdog.FAILURE_REASON_UNKNOWN); - } - - // Run handler so package failures are dispatched to observers - mTestLooper.dispatchAll(); + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); // Verify only one observer is notifed - assertEquals(1, observer1.mFailedPackages.size()); - assertEquals(APP_A, observer1.mFailedPackages.get(0)); - assertEquals(0, observer2.mFailedPackages.size()); + assertThat(observer1.mMitigatedPackages).containsExactly(APP_A); + assertThat(observer2.mMitigatedPackages).isEmpty(); } /** @@ -547,9 +572,7 @@ public class PackageWatchdogTest { // Verify we requested health checks for APP_A and APP_B List<String> requestedPackages = controller.getRequestedPackages(); - assertEquals(2, requestedPackages.size()); - assertEquals(APP_A, requestedPackages.get(0)); - assertEquals(APP_B, requestedPackages.get(1)); + assertThat(requestedPackages).containsExactly(APP_A, APP_B); // Then health check passed for APP_A (observer1 is aware) controller.setPackagePassed(APP_A); @@ -561,23 +584,19 @@ public class PackageWatchdogTest { watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), SHORT_DURATION); // Then expire observers - Thread.sleep(SHORT_DURATION); - // Run handler so package failures are dispatched to observers - mTestLooper.dispatchAll(); + moveTimeForwardAndDispatch(SHORT_DURATION); // Verify we cancelled all requests on expiry - assertEquals(0, controller.getRequestedPackages().size()); + assertThat(controller.getRequestedPackages()).isEmpty(); // Verify observer1 is not notified - assertEquals(0, observer1.mFailedPackages.size()); + assertThat(observer1.mMitigatedPackages).isEmpty(); // Verify observer2 is notifed because health checks for APP_B never passed - assertEquals(1, observer2.mFailedPackages.size()); - assertEquals(APP_B, observer2.mFailedPackages.get(0)); + assertThat(observer2.mMitigatedPackages).containsExactly(APP_B); // Verify observer3 is notifed because health checks for APP_A did not pass before expiry - assertEquals(1, observer3.mFailedPackages.size()); - assertEquals(APP_A, observer3.mFailedPackages.get(0)); + assertThat(observer3.mMitigatedPackages).containsExactly(APP_A); } /** @@ -604,9 +623,7 @@ public class PackageWatchdogTest { // Verify we requested health checks for APP_A and APP_B List<String> requestedPackages = controller.getRequestedPackages(); - assertEquals(2, requestedPackages.size()); - assertEquals(APP_A, requestedPackages.get(0)); - assertEquals(APP_B, requestedPackages.get(1)); + assertThat(requestedPackages).containsExactly(APP_A, APP_B); // Disable explicit health checks (marks APP_A and APP_B as passed) setExplicitHealthCheckEnabled(false); @@ -615,14 +632,13 @@ public class PackageWatchdogTest { mTestLooper.dispatchAll(); // Verify all checks are cancelled - assertEquals(0, controller.getRequestedPackages().size()); + assertThat(controller.getRequestedPackages()).isEmpty(); // Then expire APP_A - Thread.sleep(SHORT_DURATION); - mTestLooper.dispatchAll(); + moveTimeForwardAndDispatch(SHORT_DURATION); // Verify APP_A is not failed (APP_B) is not expired yet - assertEquals(0, observer.mFailedPackages.size()); + assertThat(observer.mMitigatedPackages).isEmpty(); // Re-enable explicit health checks setExplicitHealthCheckEnabled(true); @@ -631,7 +647,7 @@ public class PackageWatchdogTest { mTestLooper.dispatchAll(); // Verify no requests are made cos APP_A is expired and APP_B was marked as passed - assertEquals(0, controller.getRequestedPackages().size()); + assertThat(controller.getRequestedPackages()).isEmpty(); // Then set new supported packages controller.setSupportedPackages(Arrays.asList(APP_C)); @@ -643,16 +659,13 @@ public class PackageWatchdogTest { // Verify requests are only made for APP_C requestedPackages = controller.getRequestedPackages(); - assertEquals(1, requestedPackages.size()); - assertEquals(APP_C, requestedPackages.get(0)); + assertThat(requestedPackages).containsExactly(APP_C); // Then expire APP_A and APP_C - Thread.sleep(SHORT_DURATION); - mTestLooper.dispatchAll(); + moveTimeForwardAndDispatch(SHORT_DURATION); // Verify only APP_C is failed because explicit health checks was not supported for APP_A - assertEquals(1, observer.mFailedPackages.size()); - assertEquals(APP_C, observer.mFailedPackages.get(0)); + assertThat(observer.mMitigatedPackages).containsExactly(APP_C); } /** @@ -673,21 +686,48 @@ public class PackageWatchdogTest { watchdog.startObservingHealth(observer, Arrays.asList(APP_A), LONG_DURATION); // Then APP_A has exceeded health check duration - Thread.sleep(SHORT_DURATION); - mTestLooper.dispatchAll(); + moveTimeForwardAndDispatch(SHORT_DURATION); // Verify that health check is failed - assertEquals(1, observer.mFailedPackages.size()); - assertEquals(APP_A, observer.mFailedPackages.get(0)); + assertThat(observer.mMitigatedPackages).containsExactly(APP_A); - // Then clear failed packages and start observing a random package so requests are synced - // and PackageWatchdog#onSupportedPackages is called and APP_A has a chance to fail again - // this time due to package expiry. - observer.mFailedPackages.clear(); - watchdog.startObservingHealth(observer, Arrays.asList(APP_B), LONG_DURATION); + // Clear failed packages and forward time to expire the observation duration + observer.mMitigatedPackages.clear(); + moveTimeForwardAndDispatch(LONG_DURATION); // Verify that health check failure is not notified again - assertTrue(observer.mFailedPackages.isEmpty()); + assertThat(observer.mMitigatedPackages).isEmpty(); + } + + /** + * Tests failure when health check duration is different from package observation duration + * Failure is also notified only once. + */ + @Test + public void testExplicitHealthCheckFailureAfterExpiry() { + TestController controller = new TestController(); + PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */); + TestObserver observer = new TestObserver(OBSERVER_NAME_1, + PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + + // Start observing with explicit health checks for APP_A and + // package observation duration == SHORT_DURATION / 2 + // health check duration == SHORT_DURATION (set by default in the TestController) + controller.setSupportedPackages(Arrays.asList(APP_A)); + watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION / 2); + + // Forward time to expire the observation duration + moveTimeForwardAndDispatch(SHORT_DURATION / 2); + + // Verify that health check is failed + assertThat(observer.mMitigatedPackages).containsExactly(APP_A); + + // Clear failed packages and forward time to expire the health check duration + observer.mMitigatedPackages.clear(); + moveTimeForwardAndDispatch(SHORT_DURATION); + + // Verify that health check failure is not notified again + assertThat(observer.mMitigatedPackages).isEmpty(); } /** Tests {@link MonitoredPackage} health check state transitions. */ @@ -695,44 +735,46 @@ public class PackageWatchdogTest { public void testPackageHealthCheckStateTransitions() { TestController controller = new TestController(); PackageWatchdog wd = createWatchdog(controller, true /* withPackagesReady */); - MonitoredPackage m1 = wd.new MonitoredPackage(APP_A, LONG_DURATION, + MonitoredPackage m1 = wd.newMonitoredPackage(APP_A, LONG_DURATION, false /* hasPassedHealthCheck */); - MonitoredPackage m2 = wd.new MonitoredPackage(APP_B, LONG_DURATION, false); - MonitoredPackage m3 = wd.new MonitoredPackage(APP_C, LONG_DURATION, false); - MonitoredPackage m4 = wd.new MonitoredPackage(APP_D, LONG_DURATION, SHORT_DURATION, true); + MonitoredPackage m2 = wd.newMonitoredPackage(APP_B, LONG_DURATION, false); + MonitoredPackage m3 = wd.newMonitoredPackage(APP_C, LONG_DURATION, false); + MonitoredPackage m4 = wd.newMonitoredPackage(APP_D, LONG_DURATION, SHORT_DURATION, true); // Verify transition: inactive -> active -> passed // Verify initially inactive - assertEquals(MonitoredPackage.STATE_INACTIVE, m1.getHealthCheckStateLocked()); + assertThat(m1.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.INACTIVE); // Verify still inactive, until we #setHealthCheckActiveLocked - assertEquals(MonitoredPackage.STATE_INACTIVE, m1.handleElapsedTimeLocked(SHORT_DURATION)); + assertThat(m1.handleElapsedTimeLocked(SHORT_DURATION)).isEqualTo(HealthCheckState.INACTIVE); // Verify now active - assertEquals(MonitoredPackage.STATE_ACTIVE, m1.setHealthCheckActiveLocked(SHORT_DURATION)); + assertThat(m1.setHealthCheckActiveLocked(SHORT_DURATION)).isEqualTo( + HealthCheckState.ACTIVE); // Verify now passed - assertEquals(MonitoredPackage.STATE_PASSED, m1.tryPassHealthCheckLocked()); + assertThat(m1.tryPassHealthCheckLocked()).isEqualTo(HealthCheckState.PASSED); // Verify transition: inactive -> active -> failed // Verify initially inactive - assertEquals(MonitoredPackage.STATE_INACTIVE, m2.getHealthCheckStateLocked()); + assertThat(m2.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.INACTIVE); // Verify now active - assertEquals(MonitoredPackage.STATE_ACTIVE, m2.setHealthCheckActiveLocked(SHORT_DURATION)); + assertThat(m2.setHealthCheckActiveLocked(SHORT_DURATION)).isEqualTo( + HealthCheckState.ACTIVE); // Verify now failed - assertEquals(MonitoredPackage.STATE_FAILED, m2.handleElapsedTimeLocked(SHORT_DURATION)); + assertThat(m2.handleElapsedTimeLocked(SHORT_DURATION)).isEqualTo(HealthCheckState.FAILED); // Verify transition: inactive -> failed // Verify initially inactive - assertEquals(MonitoredPackage.STATE_INACTIVE, m3.getHealthCheckStateLocked()); + assertThat(m3.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.INACTIVE); // Verify now failed because package expired - assertEquals(MonitoredPackage.STATE_FAILED, m3.handleElapsedTimeLocked(LONG_DURATION)); + assertThat(m3.handleElapsedTimeLocked(LONG_DURATION)).isEqualTo(HealthCheckState.FAILED); // Verify remains failed even when asked to pass - assertEquals(MonitoredPackage.STATE_FAILED, m3.tryPassHealthCheckLocked()); + assertThat(m3.tryPassHealthCheckLocked()).isEqualTo(HealthCheckState.FAILED); // Verify transition: passed - assertEquals(MonitoredPackage.STATE_PASSED, m4.getHealthCheckStateLocked()); + assertThat(m4.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.PASSED); // Verify remains passed even if health check fails - assertEquals(MonitoredPackage.STATE_PASSED, m4.handleElapsedTimeLocked(SHORT_DURATION)); + assertThat(m4.handleElapsedTimeLocked(SHORT_DURATION)).isEqualTo(HealthCheckState.PASSED); // Verify remains passed even if package expires - assertEquals(MonitoredPackage.STATE_PASSED, m4.handleElapsedTimeLocked(LONG_DURATION)); + assertThat(m4.handleElapsedTimeLocked(LONG_DURATION)).isEqualTo(HealthCheckState.PASSED); } @Test @@ -745,52 +787,363 @@ public class PackageWatchdogTest { wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION); // Notify of NetworkStack failure - mNetworkStackCallbackCaptor.getValue().onNetworkStackFailure(APP_A); + mConnectivityModuleCallbackCaptor.getValue().onNetworkStackFailure(APP_A); // Run handler so package failures are dispatched to observers mTestLooper.dispatchAll(); // Verify the NetworkStack observer is notified - assertEquals(1, observer.mFailedPackages.size()); - assertEquals(APP_A, observer.mFailedPackages.get(0)); + assertThat(observer.mMitigatedPackages).containsExactly(APP_A); } - /** Test that observers execute correctly for different failure reasons */ + /** Test default values are used when device property is invalid. */ @Test - public void testFailureReasons() { + public void testInvalidConfig_watchdogTriggerFailureCount() { + adoptShellPermissions( + Manifest.permission.WRITE_DEVICE_CONFIG, + Manifest.permission.READ_DEVICE_CONFIG); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, + PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT, + Integer.toString(-1), /*makeDefault*/false); + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer = new TestObserver(OBSERVER_NAME_1); + + watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); + // Fail APP_A below the threshold which should not trigger package failures + for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) { + watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + } + mTestLooper.dispatchAll(); + assertThat(observer.mHealthCheckFailedPackages).isEmpty(); + + // One more to trigger the package failure + watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + mTestLooper.dispatchAll(); + assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A); + } + + /** Test default values are used when device property is invalid. */ + @Test + public void testInvalidConfig_watchdogTriggerDurationMillis() { + adoptShellPermissions( + Manifest.permission.WRITE_DEVICE_CONFIG, + Manifest.permission.READ_DEVICE_CONFIG); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, + PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT, + Integer.toString(2), /*makeDefault*/false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, + PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS, + Integer.toString(-1), /*makeDefault*/false); + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer = new TestObserver(OBSERVER_NAME_1); + + watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE); + watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + mTestLooper.dispatchAll(); + moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS + 1); + watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + mTestLooper.dispatchAll(); + + // We shouldn't receive APP_A since the interval of 2 failures is greater than + // DEFAULT_TRIGGER_FAILURE_DURATION_MS. + assertThat(observer.mHealthCheckFailedPackages).isEmpty(); + + watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + mTestLooper.dispatchAll(); + moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS - 1); + watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + mTestLooper.dispatchAll(); + + // We should receive APP_B since the interval of 2 failures is less than + // DEFAULT_TRIGGER_FAILURE_DURATION_MS. + assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_B); + } + + /** + * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered + * an invalid durationMs. + */ + @Test + public void testInvalidMonitoringDuration_beforeExpiry() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer = new TestObserver(OBSERVER_NAME_1); + + watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1); + // Note: Don't move too close to the expiration time otherwise the handler will be thrashed + // by PackageWatchdog#scheduleNextSyncStateLocked which keeps posting runnables with very + // small timeouts. + moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS - 100); + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + + // We should receive APP_A since the observer hasn't expired + assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A); + } + + /** + * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered + * an invalid durationMs. + */ + @Test + public void testInvalidMonitoringDuration_afterExpiry() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer = new TestObserver(OBSERVER_NAME_1); + + watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1); + moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS + 1); + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + + // We should receive nothing since the observer has expired + assertThat(observer.mHealthCheckFailedPackages).isEmpty(); + } + + /** Test we are notified when enough failures are triggered within any window. */ + @Test + public void testFailureTriggerWindow() { + adoptShellPermissions( + Manifest.permission.WRITE_DEVICE_CONFIG, + Manifest.permission.READ_DEVICE_CONFIG); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, + PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT, + Integer.toString(3), /*makeDefault*/false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, + PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS, + Integer.toString(1000), /*makeDefault*/false); + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer = new TestObserver(OBSERVER_NAME_1); + + watchdog.startObservingHealth(observer, Arrays.asList(APP_A), Long.MAX_VALUE); + // Raise 2 failures at t=0 and t=900 respectively + watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + mTestLooper.dispatchAll(); + moveTimeForwardAndDispatch(900); + watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + mTestLooper.dispatchAll(); + + // Raise 2 failures at t=1100 + moveTimeForwardAndDispatch(200); + watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + mTestLooper.dispatchAll(); + + // We should receive APP_A since there are 3 failures within 1000ms window + assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A); + } + + /** Test that observers execute correctly for failures reasons that go through thresholding. */ + @Test + public void testNonImmediateFailureReasons() { PackageWatchdog watchdog = createWatchdog(); TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); - TestObserver observer3 = new TestObserver(OBSERVER_NAME_3); - TestObserver observer4 = new TestObserver(OBSERVER_NAME_4); watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION); - watchdog.startObservingHealth(observer3, Arrays.asList(APP_C), SHORT_DURATION); - watchdog.startObservingHealth(observer4, Arrays.asList(APP_D), SHORT_DURATION); - for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { - watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), - PackageWatchdog.FAILURE_REASON_NATIVE_CRASH); - watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)), - PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK); - watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_C, VERSION_CODE)), - PackageWatchdog.FAILURE_REASON_APP_CRASH); - watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_D, VERSION_CODE)), - PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING); + raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, + VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_CRASH); + raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_B, + VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING); + + assertThat(observer1.getLastFailureReason()).isEqualTo( + PackageWatchdog.FAILURE_REASON_APP_CRASH); + assertThat(observer2.getLastFailureReason()).isEqualTo( + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING); + } + + /** Test that observers execute correctly for failures reasons that skip thresholding. */ + @Test + public void testImmediateFailures() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); + + watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); + + raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, + VERSION_CODE)), PackageWatchdog.FAILURE_REASON_NATIVE_CRASH); + raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_B, + VERSION_CODE)), PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK); + + assertThat(observer1.mMitigatedPackages).containsExactly(APP_A, APP_B); + } + + /** + * Test that a persistent observer will mitigate failures if it wishes to observe a package. + */ + @Test + public void testPersistentObserverWatchesPackage() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver persistentObserver = new TestObserver(OBSERVER_NAME_1); + persistentObserver.setPersistent(true); + persistentObserver.setMayObservePackages(true); + + watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION); + + raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, + VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); + assertThat(persistentObserver.mHealthCheckFailedPackages).containsExactly(APP_A); + } + + /** + * Test that a persistent observer will not mitigate failures if it does not wish to observe + * a given package. + */ + @Test + public void testPersistentObserverDoesNotWatchPackage() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver persistentObserver = new TestObserver(OBSERVER_NAME_1); + persistentObserver.setPersistent(true); + persistentObserver.setMayObservePackages(false); + + watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION); + + raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, + VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); + assertThat(persistentObserver.mHealthCheckFailedPackages).isEmpty(); + } + + + /** Ensure that boot loop mitigation is done when the number of boots meets the threshold. */ + @Test + public void testBootLoopDetection_meetsThreshold() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1); + watchdog.registerHealthObserver(bootObserver); + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { + watchdog.noteBoot(); } + assertThat(bootObserver.mitigatedBootLoop()).isTrue(); + } - // Run handler so requests are dispatched to the controller + + /** + * Ensure that boot loop mitigation is not done when the number of boots does not meet the + * threshold. + */ + @Test + public void testBootLoopDetection_doesNotMeetThreshold() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1); + watchdog.registerHealthObserver(bootObserver); + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; i++) { + watchdog.noteBoot(); + } + assertThat(bootObserver.mitigatedBootLoop()).isFalse(); + } + + /** + * Ensure that boot loop mitigation is done for the observer with the lowest user impact + */ + @Test + public void testBootLoopMitigationDoneForLowestUserImpact() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver bootObserver1 = new TestObserver(OBSERVER_NAME_1); + bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LOW); + TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2); + bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + watchdog.registerHealthObserver(bootObserver1); + watchdog.registerHealthObserver(bootObserver2); + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { + watchdog.noteBoot(); + } + assertThat(bootObserver1.mitigatedBootLoop()).isTrue(); + assertThat(bootObserver2.mitigatedBootLoop()).isFalse(); + } + + /** + * Ensure that passing a null list of failed packages does not cause any mitigation logic to + * execute. + */ + @Test + public void testNullFailedPackagesList() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); + watchdog.startObservingHealth(observer1, List.of(APP_A), LONG_DURATION); + + raiseFatalFailureAndDispatch(watchdog, null, PackageWatchdog.FAILURE_REASON_APP_CRASH); + assertThat(observer1.mMitigatedPackages).isEmpty(); + } + + /** + * Test to verify that Package Watchdog syncs health check requests with the controller + * correctly, and that the requests are only synced when the set of observed packages + * changes. + */ + @Test + public void testSyncHealthCheckRequests() { + TestController testController = spy(TestController.class); + testController.setSupportedPackages(List.of(APP_A, APP_B, APP_C)); + PackageWatchdog watchdog = createWatchdog(testController, true); + + TestObserver testObserver1 = new TestObserver(OBSERVER_NAME_1); + watchdog.registerHealthObserver(testObserver1); + watchdog.startObservingHealth(testObserver1, List.of(APP_A), LONG_DURATION); + mTestLooper.dispatchAll(); + + TestObserver testObserver2 = new TestObserver(OBSERVER_NAME_2); + watchdog.registerHealthObserver(testObserver2); + watchdog.startObservingHealth(testObserver2, List.of(APP_B), LONG_DURATION); mTestLooper.dispatchAll(); - assertTrue(observer1.getLastFailureReason() - == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH); - assertTrue(observer2.getLastFailureReason() - == PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK); - assertTrue(observer3.getLastFailureReason() - == PackageWatchdog.FAILURE_REASON_APP_CRASH); - assertTrue(observer4.getLastFailureReason() - == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING); + TestObserver testObserver3 = new TestObserver(OBSERVER_NAME_3); + watchdog.registerHealthObserver(testObserver3); + watchdog.startObservingHealth(testObserver3, List.of(APP_C), LONG_DURATION); + mTestLooper.dispatchAll(); + + watchdog.unregisterHealthObserver(testObserver1); + mTestLooper.dispatchAll(); + + watchdog.unregisterHealthObserver(testObserver2); + mTestLooper.dispatchAll(); + + watchdog.unregisterHealthObserver(testObserver3); + mTestLooper.dispatchAll(); + + List<Set> expectedSyncRequests = List.of( + Set.of(), + Set.of(APP_A), + Set.of(APP_A, APP_B), + Set.of(APP_A, APP_B, APP_C), + Set.of(APP_B, APP_C), + Set.of(APP_C), + Set.of() + ); + assertThat(testController.getSyncRequests()).isEqualTo(expectedSyncRequests); + } + + /** + * Ensure that the failure history of a package is preserved when making duplicate calls to + * observe the package. + */ + @Test + public void testFailureHistoryIsPreserved() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer = new TestObserver(OBSERVER_NAME_1); + watchdog.startObservingHealth(observer, List.of(APP_A), SHORT_DURATION); + for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) { + watchdog.onPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + } + mTestLooper.dispatchAll(); + assertThat(observer.mMitigatedPackages).isEmpty(); + watchdog.startObservingHealth(observer, List.of(APP_A), LONG_DURATION); + watchdog.onPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + mTestLooper.dispatchAll(); + assertThat(observer.mMitigatedPackages).isEqualTo(List.of(APP_A)); } private void adoptShellPermissions(String... permissions) { @@ -819,6 +1172,26 @@ public class PackageWatchdogTest { } } + private void moveTimeForwardAndDispatch(long milliSeconds) { + mTestClock.moveTimeForward(milliSeconds); + mTestLooper.moveTimeForward(milliSeconds); + mTestLooper.dispatchAll(); + } + + /** Trigger package failures above the threshold. */ + private void raiseFatalFailureAndDispatch(PackageWatchdog watchdog, + List<VersionedPackage> packages, int failureReason) { + long triggerFailureCount = watchdog.getTriggerFailureCount(); + if (failureReason == PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK + || failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) { + triggerFailureCount = 1; + } + for (int i = 0; i < triggerFailureCount; i++) { + watchdog.onPackageFailure(packages, failureReason); + } + mTestLooper.dispatchAll(); + } + private PackageWatchdog createWatchdog() { return createWatchdog(new TestController(), true /* withPackagesReady */); } @@ -829,18 +1202,18 @@ public class PackageWatchdogTest { Handler handler = new Handler(mTestLooper.getLooper()); PackageWatchdog watchdog = new PackageWatchdog(mSpyContext, policyFile, handler, handler, controller, - mMockNetworkStackClient); + mConnectivityModuleConnector, mTestClock); // Verify controller is not automatically started - assertFalse(controller.mIsEnabled); + assertThat(controller.mIsEnabled).isFalse(); if (withPackagesReady) { // Only capture the NetworkStack callback for the latest registered watchdog - reset(mMockNetworkStackClient); + reset(mConnectivityModuleConnector); watchdog.onPackagesReady(); // Verify controller by default is started when packages are ready - assertTrue(controller.mIsEnabled); + assertThat(controller.mIsEnabled).isTrue(); - verify(mMockNetworkStackClient).registerHealthListener( - mNetworkStackCallbackCaptor.capture()); + verify(mConnectivityModuleConnector).registerHealthListener( + mConnectivityModuleCallbackCaptor.capture()); } return watchdog; } @@ -849,7 +1222,11 @@ public class PackageWatchdogTest { private final String mName; private int mImpact; private int mLastFailureReason; - final List<String> mFailedPackages = new ArrayList<>(); + private boolean mIsPersistent = false; + private boolean mMayObservePackages = false; + private boolean mMitigatedBootLoop = false; + final List<String> mHealthCheckFailedPackages = new ArrayList<>(); + final List<String> mMitigatedPackages = new ArrayList<>(); TestObserver(String name) { mName = name; @@ -861,12 +1238,13 @@ public class PackageWatchdogTest { mImpact = impact; } - public int onHealthCheckFailed(VersionedPackage versionedPackage) { + public int onHealthCheckFailed(VersionedPackage versionedPackage, int failureReason) { + mHealthCheckFailedPackages.add(versionedPackage.getPackageName()); return mImpact; } public boolean execute(VersionedPackage versionedPackage, int failureReason) { - mFailedPackages.add(versionedPackage.getPackageName()); + mMitigatedPackages.add(versionedPackage.getPackageName()); mLastFailureReason = failureReason; return true; } @@ -875,9 +1253,42 @@ public class PackageWatchdogTest { return mName; } + public boolean isPersistent() { + return mIsPersistent; + } + + public boolean mayObservePackage(String packageName) { + return mMayObservePackages; + } + + public int onBootLoop() { + return mImpact; + } + + public boolean executeBootLoopMitigation() { + mMitigatedBootLoop = true; + return true; + } + + public boolean mitigatedBootLoop() { + return mMitigatedBootLoop; + } + public int getLastFailureReason() { return mLastFailureReason; } + + public void setPersistent(boolean persistent) { + mIsPersistent = persistent; + } + + public void setImpact(int impact) { + mImpact = impact; + } + + public void setMayObservePackages(boolean mayObservePackages) { + mMayObservePackages = mayObservePackages; + } } private static class TestController extends ExplicitHealthCheckController { @@ -891,6 +1302,7 @@ public class PackageWatchdogTest { private Consumer<String> mPassedConsumer; private Consumer<List<PackageConfig>> mSupportedConsumer; private Runnable mNotifySyncRunnable; + private List<Set> mSyncRequests = new ArrayList<>(); @Override public void setEnabled(boolean enabled) { @@ -910,6 +1322,7 @@ public class PackageWatchdogTest { @Override public void syncRequests(Set<String> packages) { + mSyncRequests.add(packages); mRequestedPackages.clear(); if (mIsEnabled) { packages.retainAll(mSupportedPackages); @@ -940,5 +1353,22 @@ public class PackageWatchdogTest { return Collections.emptyList(); } } + + public List<Set> getSyncRequests() { + return mSyncRequests; + } + } + + private static class TestClock implements PackageWatchdog.SystemClock { + // Note 0 is special to the internal clock of PackageWatchdog. We need to start from + // a non-zero value in order not to disrupt the logic of PackageWatchdog. + private long mUpTimeMillis = 1; + @Override + public long uptimeMillis() { + return mUpTimeMillis; + } + public void moveTimeForward(long milliSeconds) { + mUpTimeMillis += milliSeconds; + } } } diff --git a/tests/PlatformCompatGating/Android.bp b/tests/PlatformCompatGating/Android.bp new file mode 100644 index 000000000000..7d918cc4c18b --- /dev/null +++ b/tests/PlatformCompatGating/Android.bp @@ -0,0 +1,35 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +android_test { + name: "PlatformCompatGating", + // Only compile source java files in this apk. + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + test_suites: ["device-tests"], + static_libs: [ + "junit", + "androidx.test.runner", + "androidx.test.core", + "androidx.test.ext.junit", + "mockito-target-minus-junit4", + "testng", + "truth-prebuilt", + "platform-compat-test-rules", + ], +} diff --git a/tests/RcsTests/AndroidManifest.xml b/tests/PlatformCompatGating/AndroidManifest.xml index b1706a0a3629..0510db2e52aa 100644 --- a/tests/RcsTests/AndroidManifest.xml +++ b/tests/PlatformCompatGating/AndroidManifest.xml @@ -1,11 +1,16 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.tests.rcs"> - <application android:label="RCS Test"> + package="com.android.tests.gating"> + + <queries> + <package android:name="com.android.tests.gating.app_not_installed" /> + </queries> + + <application android:label="GatingTest"> <uses-library android:name="android.test.runner" /> </application> <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="com.android.tests.rcs"/> + android:targetPackage="com.android.tests.gating"/> </manifest> diff --git a/tests/PlatformCompatGating/AndroidTest.xml b/tests/PlatformCompatGating/AndroidTest.xml new file mode 100644 index 000000000000..0c7485b27fb8 --- /dev/null +++ b/tests/PlatformCompatGating/AndroidTest.xml @@ -0,0 +1,29 @@ +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Test compatibility change gating."> + <target_preparer class="com.android.tradefed.targetprep.TestFilePushSetup"/> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="PlatformCompatGating.apk"/> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"/> + <option name="test-suite-tag" value="apct"/> + <option name="test-tag" value="Gating"/> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.tests.gating"/> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/tests/PlatformCompatGating/src/com/android/compat/testing/DummyApi.java b/tests/PlatformCompatGating/src/com/android/compat/testing/DummyApi.java new file mode 100644 index 000000000000..731be8e3d9f0 --- /dev/null +++ b/tests/PlatformCompatGating/src/com/android/compat/testing/DummyApi.java @@ -0,0 +1,87 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compat.testing; + +import android.compat.Compatibility; +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; + +import com.android.internal.compat.IPlatformCompat; + +/** + * This is a dummy API to test gating + * + * @hide + */ +public class DummyApi { + + public static final long CHANGE_ID = 666013; + public static final long CHANGE_ID_1 = 666014; + public static final long CHANGE_ID_2 = 666015; + public static final long CHANGE_SYSTEM_SERVER = 666016; + + /** + * Dummy method + * @return "A" if change is enabled, "B" otherwise. + */ + public static String dummyFunc() { + if (Compatibility.isChangeEnabled(CHANGE_ID)) { + return "A"; + } + return "B"; + } + + /** + * Dummy combined method + * @return "0" if {@link CHANGE_ID_1} is disabled and {@link CHANGE_ID_2} is disabled, + "1" if {@link CHANGE_ID_1} is disabled and {@link CHANGE_ID_2} is enabled, + "2" if {@link CHANGE_ID_1} is enabled and {@link CHANGE_ID_2} is disabled, + "3" if {@link CHANGE_ID_1} is enabled and {@link CHANGE_ID_2} is enabled. + */ + public static String dummyCombinedFunc() { + if (!Compatibility.isChangeEnabled(CHANGE_ID_1) + && !Compatibility.isChangeEnabled(CHANGE_ID_2)) { + return "0"; + } else if (!Compatibility.isChangeEnabled(CHANGE_ID_1) + && Compatibility.isChangeEnabled(CHANGE_ID_2)) { + return "1"; + } else if (Compatibility.isChangeEnabled(CHANGE_ID_1) + && !Compatibility.isChangeEnabled(CHANGE_ID_2)) { + return "2"; + } + return "3"; + } + + /** + * Dummy api using system server API. + */ + public static boolean dummySystemServer(Context context) { + IPlatformCompat platformCompat = IPlatformCompat.Stub + .asInterface(ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + if (platformCompat == null) { + throw new RuntimeException("Could not obtain IPlatformCompat instance!"); + } + String packageName = context.getPackageName(); + try { + return platformCompat.isChangeEnabledByPackageName(CHANGE_SYSTEM_SERVER, packageName, + context.getUserId()); + } catch (RemoteException e) { + throw new RuntimeException("Could not get change value!", e); + } + } +} diff --git a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt new file mode 100644 index 000000000000..0f62c4fa66a3 --- /dev/null +++ b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tests.gating + +import android.Manifest +import android.app.UiAutomation +import android.content.Context +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.pm.parsing.result.ParseInput +import android.os.Build +import android.os.ParcelFileDescriptor +import android.os.ServiceManager +import androidx.test.platform.app.InstrumentationRegistry +import com.android.internal.compat.IPlatformCompat +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Assume.assumeTrue +import org.junit.Before +import org.junit.BeforeClass +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.testng.Assert.assertThrows +import java.io.FileReader + +/** + * Verifies the shell commands "am compat enable/disable/reset" against a real server change ID + * for a not installed package. + * + * This class intentionally does not use any PlatformCompat testing infrastructure since that could + * interfere with what it's testing. + */ +@RunWith(Parameterized::class) +class PlatformCompatCommandNotInstalledTest { + + companion object { + + private const val TEST_PKG = "com.android.tests.gating.app_not_installed" + private const val TEST_CHANGE_ID = ParseInput.DeferredError.MISSING_APP_TAG + + private val instrumentation = InstrumentationRegistry.getInstrumentation() + + @JvmStatic + @BeforeClass + fun assumeDebuggable() { + assumeTrue(Build.IS_DEBUGGABLE) + } + + @JvmStatic + @BeforeClass + fun assertNotInstalled() { + assertThrows(PackageManager.NameNotFoundException::class.java) { + instrumentation.context.packageManager + .getApplicationInfo(TEST_PKG, PackageManager.MATCH_ALL) + } + } + + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun parameters() = arrayOf( + Params(enableDisable = null, targetSdk = 29, result = false), + Params(enableDisable = null, targetSdk = 30, result = true), + + Params(enableDisable = true, targetSdk = 29, result = true), + Params(enableDisable = true, targetSdk = 30, result = true), + + Params(enableDisable = false, targetSdk = 29, result = false), + Params(enableDisable = false, targetSdk = 30, result = false) + ) + } + + data class Params(val enableDisable: Boolean?, val targetSdk: Int, val result: Boolean) + + @Parameterized.Parameter(0) + lateinit var params: Params + + private val uiAutomation: UiAutomation = instrumentation.getUiAutomation() + private val platformCompat = IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)) + + @Before + fun resetChangeId() { + uiAutomation.adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE, + Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG, + Manifest.permission.READ_COMPAT_CHANGE_CONFIG) + + val result = command("am compat reset $TEST_CHANGE_ID $TEST_PKG") + assertThat(result.startsWith("Reset change") || result.startsWith("No override")) + .isTrue() + } + + fun ParcelFileDescriptor.text() = FileReader(fileDescriptor).readText() + + @After + fun resetIdentity() = uiAutomation.dropShellPermissionIdentity() + + @Test + fun execute() { + when (params.enableDisable) { + null -> { /* do nothing */ + } + true -> assertThat(command("am compat enable $TEST_CHANGE_ID $TEST_PKG")) + .startsWith("Enabled change") + false -> assertThat(command("am compat disable $TEST_CHANGE_ID $TEST_PKG")) + .startsWith("Disabled change") + } + + val appInfo = ApplicationInfo().apply { + this.packageName = TEST_PKG + this.targetSdkVersion = params.targetSdk + } + + assertThat(platformCompat.isChangeEnabled(TEST_CHANGE_ID, appInfo)).isEqualTo(params.result) + } + + private fun command(command: String) = + FileReader(uiAutomation.executeShellCommand(command).fileDescriptor).readText() +} diff --git a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatGatingTest.java b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatGatingTest.java new file mode 100644 index 000000000000..c1ce0e99640c --- /dev/null +++ b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatGatingTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tests.gating; + +import static com.google.common.truth.Truth.assertThat; + +import android.compat.testing.PlatformCompatChangeRule; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.compat.testing.DummyApi; + +import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; + +/** + * Tests for platform compatibility change gating. + */ +@RunWith(AndroidJUnit4.class) +public class PlatformCompatGatingTest { + + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + + @Test + @EnableCompatChanges({DummyApi.CHANGE_ID}) + public void testDummyGatingPositive() { + assertThat(DummyApi.dummyFunc()).isEqualTo("A"); + } + + @Test + @DisableCompatChanges({DummyApi.CHANGE_ID}) + public void testDummyGatingNegative() { + assertThat(DummyApi.dummyFunc()).isEqualTo("B"); + } + + @Test + @DisableCompatChanges({DummyApi.CHANGE_ID_1, DummyApi.CHANGE_ID_2}) + public void testDummyGatingCombined0() { + assertThat(DummyApi.dummyCombinedFunc()).isEqualTo("0"); + } + + @Test + @DisableCompatChanges({DummyApi.CHANGE_ID_1}) + @EnableCompatChanges({DummyApi.CHANGE_ID_2}) + public void testDummyGatingCombined1() { + assertThat(DummyApi.dummyCombinedFunc()).isEqualTo("1"); + } + + @Test + @EnableCompatChanges({DummyApi.CHANGE_ID_1}) + @DisableCompatChanges({DummyApi.CHANGE_ID_2}) + public void testDummyGatingCombined2() { + assertThat(DummyApi.dummyCombinedFunc()).isEqualTo("2"); + } + + @Test + @EnableCompatChanges({DummyApi.CHANGE_ID_1, DummyApi.CHANGE_ID_2}) + public void testDummyGatingCombined3() { + assertThat(DummyApi.dummyCombinedFunc()).isEqualTo("3"); + } + + @Test + @EnableCompatChanges({DummyApi.CHANGE_SYSTEM_SERVER}) + public void testDummyGatingPositiveSystemServer() { + assertThat(DummyApi.dummySystemServer( + InstrumentationRegistry.getInstrumentation().getTargetContext())).isTrue(); + } + + @Test + @DisableCompatChanges({DummyApi.CHANGE_SYSTEM_SERVER}) + public void testDummyGatingNegativeSystemServer() { + assertThat(DummyApi.dummySystemServer( + InstrumentationRegistry.getInstrumentation().getTargetContext())).isFalse(); + } +} diff --git a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatPermissionsTest.java b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatPermissionsTest.java new file mode 100644 index 000000000000..9b9e5815a588 --- /dev/null +++ b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatPermissionsTest.java @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tests.gating; + +import static android.Manifest.permission.LOG_COMPAT_CHANGE; +import static android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG; +import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG; + +import android.app.Instrumentation; +import android.app.UiAutomation; +import android.compat.Compatibility.ChangeConfig; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Process; +import android.os.ServiceManager; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.internal.compat.CompatibilityChangeConfig; +import com.android.internal.compat.IPlatformCompat; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.HashSet; +import java.util.Set; + +@RunWith(JUnit4.class) +public final class PlatformCompatPermissionsTest { + + // private Context mContext; + private IPlatformCompat mPlatformCompat; + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + private Context mContext; + private UiAutomation mUiAutomation; + private PackageManager mPackageManager; + + @Before + public void setUp() { + // mContext; + mPlatformCompat = IPlatformCompat.Stub + .asInterface(ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + mUiAutomation = instrumentation.getUiAutomation(); + mContext = instrumentation.getTargetContext(); + + mPackageManager = mContext.getPackageManager(); + } + + @After + public void tearDown() { + + mUiAutomation.dropShellPermissionIdentity(); + } + + @Test + public void reportChange_noLogCompatChangePermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.reportChange(1, mPackageManager.getApplicationInfo(packageName, 0)); + } + + @Test + public void reportChange_logCompatChangePermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(LOG_COMPAT_CHANGE); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.reportChange(1, mPackageManager.getApplicationInfo(packageName, 0)); + } + + @Test + public void reportChangeByPackageName_noLogCompatChangePermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.reportChangeByPackageName(1, packageName, 0); + } + + @Test + public void reportChangeByPackageName_logCompatChangePermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(LOG_COMPAT_CHANGE); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.reportChangeByPackageName(1, packageName, 0); + } + + @Test + public void reportChangeByUid_noLogCompatChangePermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + + mPlatformCompat.reportChangeByUid(1, Process.myUid()); + } + + @Test + public void reportChangeByUid_logCompatChangePermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(LOG_COMPAT_CHANGE); + + mPlatformCompat.reportChangeByUid(1, Process.myUid()); + } + + @Test + public void isChangeEnabled_noReadCompatConfigPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.isChangeEnabled(1, mPackageManager.getApplicationInfo(packageName, 0)); + } + + @Test + public void isChangeEnabled_noLogCompatChangeConfigPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.isChangeEnabled(1, mPackageManager.getApplicationInfo(packageName, 0)); + } + + @Test + public void isChangeEnabled_readAndLogCompatChangeConfigPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.isChangeEnabled(1, mPackageManager.getApplicationInfo(packageName, 0)); + } + + @Test + public void isChangeEnabledByPackageName_noReadCompatConfigPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.isChangeEnabledByPackageName(1, packageName, 0); + } + + @Test + public void isChangeEnabledByPackageName_noLogompatConfigPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.isChangeEnabledByPackageName(1, packageName, 0); + } + + @Test + public void isChangeEnabledByPackageName_readAndLogCompatChangeConfigPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.isChangeEnabledByPackageName(1, packageName, 0); + } + + @Test + public void isChangeEnabledByUid_noReadCompatConfigPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + + mPlatformCompat.isChangeEnabledByUid(1, Process.myUid()); + } + + @Test + public void isChangeEnabledByUid_noLogCompatChangePermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG); + + mPlatformCompat.isChangeEnabledByUid(1, Process.myUid()); + } + + @Test + public void isChangeEnabledByUid_readAndLogCompatChangeConfigPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE); + + mPlatformCompat.isChangeEnabledByUid(1, Process.myUid()); + } + + @Test + public void setOverrides_noOverridesPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + Set<Long> enabled = new HashSet<>(); + Set<Long> disabled = new HashSet<>(); + ChangeConfig changeConfig = new ChangeConfig(enabled, disabled); + CompatibilityChangeConfig compatibilityChangeConfig = + new CompatibilityChangeConfig(changeConfig); + + mPlatformCompat.setOverrides(compatibilityChangeConfig, "foo.bar"); + } + @Test + public void setOverrides_overridesPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(OVERRIDE_COMPAT_CHANGE_CONFIG); + Set<Long> enabled = new HashSet<>(); + Set<Long> disabled = new HashSet<>(); + ChangeConfig changeConfig = new ChangeConfig(enabled, disabled); + CompatibilityChangeConfig compatibilityChangeConfig = + new CompatibilityChangeConfig(changeConfig); + + mPlatformCompat.setOverrides(compatibilityChangeConfig, "foo.bar"); + } + + @Test + public void setOverridesForTest_noOverridesPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + Set<Long> enabled = new HashSet<>(); + Set<Long> disabled = new HashSet<>(); + ChangeConfig changeConfig = new ChangeConfig(enabled, disabled); + CompatibilityChangeConfig compatibilityChangeConfig = + new CompatibilityChangeConfig(changeConfig); + + mPlatformCompat.setOverridesForTest(compatibilityChangeConfig, "foo.bar"); + } + @Test + public void setOverridesForTest_overridesPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(OVERRIDE_COMPAT_CHANGE_CONFIG); + Set<Long> enabled = new HashSet<>(); + Set<Long> disabled = new HashSet<>(); + ChangeConfig changeConfig = new ChangeConfig(enabled, disabled); + CompatibilityChangeConfig compatibilityChangeConfig = + new CompatibilityChangeConfig(changeConfig); + + mPlatformCompat.setOverridesForTest(compatibilityChangeConfig, "foo.bar"); + } + + @Test + public void clearOverrides_noOverridesPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + + mPlatformCompat.clearOverrides("foo.bar"); + } + @Test + public void clearOverrides_overridesPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(OVERRIDE_COMPAT_CHANGE_CONFIG); + + mPlatformCompat.clearOverrides("foo.bar"); + } + + @Test + public void clearOverridesForTest_noOverridesPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + + mPlatformCompat.clearOverridesForTest("foo.bar"); + } + @Test + public void clearOverridesForTest_overridesPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(OVERRIDE_COMPAT_CHANGE_CONFIG); + + mPlatformCompat.clearOverridesForTest("foo.bar"); + } + + @Test + public void clearOverride_noOverridesPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + + mPlatformCompat.clearOverride(1, "foo.bar"); + } + @Test + public void clearOverride_overridesPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(OVERRIDE_COMPAT_CHANGE_CONFIG); + + mPlatformCompat.clearOverride(1, "foo.bar"); + } + + @Test + public void listAllChanges_noReadCompatConfigPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + + mPlatformCompat.listAllChanges(); + } + @Test + public void listAllChanges_readCompatConfigPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG); + + mPlatformCompat.listAllChanges(); + } +} diff --git a/tests/FlickerTests/lib/Android.bp b/tests/PlatformCompatGating/test-rules/Android.bp index 5d8ed2c205e9..10fa2dc0d7c6 100644 --- a/tests/FlickerTests/lib/Android.bp +++ b/tests/PlatformCompatGating/test-rules/Android.bp @@ -1,5 +1,5 @@ // -// Copyright (C) 2018 The Android Open Source Project +// Copyright (C) 2019 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,31 +14,13 @@ // limitations under the License. // -java_test { - name: "flickerlib", - platform_apis: true, +java_library { + name: "platform-compat-test-rules", srcs: ["src/**/*.java"], static_libs: [ - "androidx.test.janktesthelper", - "cts-wm-util", - "platformprotosnano", - "layersprotosnano", + "junit", + "androidx.test.core", "truth-prebuilt", - "sysui-helper", - "launcher-helper-lib", - ], -} - -java_library { - name: "flickerautomationhelperlib", - sdk_version: "test_current", - srcs: [ - "src/com/android/server/wm/flicker/AutomationUtils.java", - "src/com/android/server/wm/flicker/WindowUtils.java", - ], - static_libs: [ - "sysui-helper", - "launcher-helper-lib", - "compatibility-device-util-axt", + "core-compat-test-rules" ], -} +}
\ No newline at end of file diff --git a/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java b/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java new file mode 100644 index 000000000000..d6846faa5c00 --- /dev/null +++ b/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.compat.testing; + +import android.Manifest; +import android.app.Instrumentation; +import android.app.UiAutomation; +import android.compat.Compatibility; +import android.compat.Compatibility.ChangeConfig; +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; + +import androidx.test.platform.app.InstrumentationRegistry; + + +import com.android.internal.compat.CompatibilityChangeConfig; +import com.android.internal.compat.IPlatformCompat; + +import libcore.junit.util.compat.CoreCompatChangeRule; + +import org.junit.runners.model.Statement; + +/** + * Allows tests to specify the which change to disable. + * + * <p>To use add the following to the test class. It will only change the behavior of a test method + * if it is annotated with + * {@link libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges} and/or + * {@link libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges}. + * </p> + * <pre> + * @Rule + * public TestRule compatChangeRule = new PlatformCompatChangeRule(); + * </pre> + * + * <p>Each test method that needs to disable a specific change needs to be annotated + * with {@link libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges} and/or + * {@link libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges} specifying the change + * id. e.g.: + * </p> + * <pre> + * @Test + * @DisableCompatChanges({42}) + * public void testAsIfChange42Disabled() { + * // check behavior + * } + * + * @Test + * @EnableCompatChanges({42}) + * public void testAsIfChange42Enabled() { + * // check behavior + * + * </pre> + */ +public class PlatformCompatChangeRule extends CoreCompatChangeRule { + + @Override + protected Statement createStatementForConfig(final Statement statement, ChangeConfig config) { + return new CompatChangeStatement(statement, config); + } + + + private static class CompatChangeStatement extends Statement { + private final Statement mTestStatement; + private final ChangeConfig mConfig; + + private CompatChangeStatement(Statement testStatement, ChangeConfig config) { + this.mTestStatement = testStatement; + this.mConfig = config; + } + + @Override + public void evaluate() throws Throwable { + Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + UiAutomation uiAutomation = instrumentation.getUiAutomation(); + String packageName = instrumentation.getTargetContext().getPackageName(); + IPlatformCompat platformCompat = IPlatformCompat.Stub + .asInterface(ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + if (platformCompat == null) { + throw new IllegalStateException("Could not get IPlatformCompat service!"); + } + uiAutomation.adoptShellPermissionIdentity( + Manifest.permission.LOG_COMPAT_CHANGE, + Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG, + Manifest.permission.READ_COMPAT_CHANGE_CONFIG); + Compatibility.setOverrides(mConfig); + try { + platformCompat.setOverridesForTest(new CompatibilityChangeConfig(mConfig), + packageName); + try { + mTestStatement.evaluate(); + } finally { + platformCompat.clearOverridesForTest(packageName); + } + } catch (RemoteException e) { + throw new RuntimeException("Could not call IPlatformCompat binder method!", e); + } finally { + uiAutomation.dropShellPermissionIdentity(); + Compatibility.clearOverrides(); + } + } + } +} diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBoolTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBoolTest.java index c21c4033a0ff..3415d2e13974 100644 --- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBoolTest.java +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBoolTest.java @@ -384,55 +384,55 @@ public class ProtoInputStreamBoolTest extends TestCase { }; ProtoInputStream pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readFloat(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readDouble(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readInt(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readLong(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBytes(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readString(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBytesTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBytesTest.java index 09fe40edda6c..8796807c0521 100644 --- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBytesTest.java +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBytesTest.java @@ -306,55 +306,55 @@ public class ProtoInputStreamBytesTest extends TestCase { }; ProtoInputStream pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readFloat(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readDouble(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readInt(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readLong(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBoolean(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readString(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamDoubleTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamDoubleTest.java index 118fe3431e01..2b54e960eabb 100644 --- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamDoubleTest.java +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamDoubleTest.java @@ -611,55 +611,55 @@ public class ProtoInputStreamDoubleTest extends TestCase { }; ProtoInputStream pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readFloat(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBoolean(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readInt(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readLong(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBytes(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readString(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamEnumTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamEnumTest.java index f55d95129588..19bad7099622 100644 --- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamEnumTest.java +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamEnumTest.java @@ -454,55 +454,55 @@ public class ProtoInputStreamEnumTest extends TestCase { }; ProtoInputStream pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readFloat(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readDouble(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBoolean(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readLong(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBytes(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readString(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed32Test.java index df68476f0c36..2bc61a0c7e8a 100644 --- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed32Test.java +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed32Test.java @@ -431,55 +431,55 @@ public class ProtoInputStreamFixed32Test extends TestCase { }; ProtoInputStream pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readFloat(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readDouble(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBoolean(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readLong(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBytes(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readString(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed64Test.java index af4130b28cd8..a54ecf99d62c 100644 --- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed64Test.java +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed64Test.java @@ -532,55 +532,55 @@ public class ProtoInputStreamFixed64Test extends TestCase { }; ProtoInputStream pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readFloat(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readDouble(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readInt(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBoolean(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBytes(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readString(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFloatTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFloatTest.java index 9bc07dc513e1..0477e9ea74e0 100644 --- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFloatTest.java +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFloatTest.java @@ -563,55 +563,55 @@ public class ProtoInputStreamFloatTest extends TestCase { }; ProtoInputStream pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBoolean(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readDouble(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readInt(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readLong(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBytes(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readString(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamIncompleteValueTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamIncompleteValueTest.java new file mode 100644 index 000000000000..167d5a438302 --- /dev/null +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamIncompleteValueTest.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.protoinputstream; + +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoParseException; +import android.util.proto.ProtoStream; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ProtoInputStreamIncompleteValueTest extends TestCase { + + /** + * Test that an incomplete varint at the end of a stream throws an exception + */ + public void testIncompleteVarint() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 : varint -> invalid varint value + (byte) 0x08, + (byte) 0xff, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream); + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + try { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + pi.readInt(fieldId1); + fail("Should have thrown a ProtoParseException"); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } catch (ProtoParseException ppe) { + // good + stream.close(); + return; + } + } + stream.close(); + fail("Test should not have reached this point..."); + } + + /** + * Test that an incomplete fixed64 at the end of a stream throws an exception + */ + public void testIncompleteFixed64() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED64; + + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 2 -> invalid fixed64 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream); + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + try { + switch (pi.getFieldNumber()) { + case (int) fieldId2: + pi.readLong(fieldId2); + fail("Should have thrown a ProtoParseException"); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } catch (ProtoParseException ppe) { + // good + stream.close(); + return; + } + } + stream.close(); + fail("Test should not have reached this point..."); + } + + /** + * Test that an incomplete length delimited value at the end of a stream throws an exception + */ + public void testIncompleteLengthDelimited() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BYTES; + + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 5 -> invalid byte array (has size 5 but only 4 values) + (byte) 0x2a, + (byte) 0x05, + (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream); + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + try { + switch (pi.getFieldNumber()) { + case (int) fieldId5: + pi.readBytes(fieldId5); + fail("Should have thrown a ProtoParseException"); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } catch (ProtoParseException ppe) { + // good + stream.close(); + return; + } + } + stream.close(); + fail("Test should not have reached this point..."); + } + + /** + * Test that an incomplete fixed32 at the end of a stream throws an exception + */ + public void testIncompleteFixed32() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED32; + + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 2 -> invalid fixed32 + (byte) 0x15, + (byte) 0x01, (byte) 0x00, (byte) 0x00, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream); + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + try { + switch (pi.getFieldNumber()) { + case (int) fieldId2: + pi.readInt(fieldId2); + fail("Should have thrown a ProtoParseException"); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } catch (ProtoParseException ppe) { + // good + stream.close(); + return; + } + } + stream.close(); + fail("Test should not have reached this point..."); + } +} diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt32Test.java index 0065870486f2..a7f3f65e504d 100644 --- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt32Test.java +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt32Test.java @@ -449,55 +449,55 @@ public class ProtoInputStreamInt32Test extends TestCase { }; ProtoInputStream pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readFloat(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readDouble(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBoolean(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readLong(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBytes(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readString(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt64Test.java index 4d6d105e60b0..dc42468e0605 100644 --- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt64Test.java +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt64Test.java @@ -529,55 +529,55 @@ public class ProtoInputStreamInt64Test extends TestCase { }; ProtoInputStream pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readFloat(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readDouble(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readInt(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBoolean(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBytes(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readString(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamObjectTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamObjectTest.java index 5e49eeafb8af..1c0832e3e676 100644 --- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamObjectTest.java +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamObjectTest.java @@ -391,55 +391,55 @@ public class ProtoInputStreamObjectTest extends TestCase { }; ProtoInputStream pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readFloat(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readDouble(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readInt(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readLong(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBoolean(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readString(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed32Test.java index 75c88a44614b..d349ea2baa67 100644 --- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed32Test.java +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed32Test.java @@ -431,55 +431,55 @@ public class ProtoInputStreamSFixed32Test extends TestCase { }; ProtoInputStream pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readFloat(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readDouble(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBoolean(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readLong(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBytes(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readString(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed64Test.java index 4c65cf49318d..81a9c591b32e 100644 --- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed64Test.java +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed64Test.java @@ -531,55 +531,55 @@ public class ProtoInputStreamSFixed64Test extends TestCase { }; ProtoInputStream pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readFloat(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readDouble(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readInt(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBoolean(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBytes(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readString(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt32Test.java index 6854cd8aad28..97194444deb2 100644 --- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt32Test.java +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt32Test.java @@ -431,55 +431,55 @@ public class ProtoInputStreamSInt32Test extends TestCase { }; ProtoInputStream pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readFloat(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readDouble(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBoolean(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readLong(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBytes(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readString(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt64Test.java index c53e9d72562a..118476cdf235 100644 --- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt64Test.java +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt64Test.java @@ -506,55 +506,55 @@ public class ProtoInputStreamSInt64Test extends TestCase { }; ProtoInputStream pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readFloat(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readDouble(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readInt(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBoolean(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBytes(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readString(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamStringTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamStringTest.java index 816d5f900a3d..51ee78f32767 100644 --- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamStringTest.java +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamStringTest.java @@ -287,55 +287,55 @@ public class ProtoInputStreamStringTest extends TestCase { }; ProtoInputStream pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readFloat(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readDouble(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readInt(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readLong(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBytes(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBoolean(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt32Test.java index 50fc537767a4..42f3e991f6e8 100644 --- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt32Test.java +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt32Test.java @@ -448,55 +448,55 @@ public class ProtoInputStreamUInt32Test extends TestCase { }; ProtoInputStream pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readFloat(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readDouble(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBoolean(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readLong(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBytes(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readString(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt64Test.java index 20969e9056a9..8ba2c0ccaca9 100644 --- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt64Test.java +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt64Test.java @@ -525,55 +525,55 @@ public class ProtoInputStreamUInt64Test extends TestCase { }; ProtoInputStream pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readFloat(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readDouble(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readInt(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBoolean(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readBytes(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } pi = new ProtoInputStream(protobuf); - pi.isNextField(fieldId1); + pi.nextField(); try { pi.readString(fieldId1); - fail("Should have throw IllegalArgumentException"); + fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException iae) { // good } diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoTests.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoTests.java index cdf6ae20f370..685110c08147 100644 --- a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoTests.java +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoTests.java @@ -39,6 +39,7 @@ public class ProtoTests { suite.addTestSuite(ProtoInputStreamBytesTest.class); suite.addTestSuite(ProtoInputStreamEnumTest.class); suite.addTestSuite(ProtoInputStreamObjectTest.class); + suite.addTestSuite(ProtoInputStreamIncompleteValueTest.class); return suite; } diff --git a/tests/RcsTests/Android.bp b/tests/RcsTests/Android.bp deleted file mode 100644 index 8ee496066bb3..000000000000 --- a/tests/RcsTests/Android.bp +++ /dev/null @@ -1,17 +0,0 @@ -android_test { - name: "RcsTests", - // Only compile source java files in this apk. - srcs: ["src/**/*.java"], - platform_apis: true, - certificate: "platform", - libs: [ - "android.test.runner", - "android.test.base", - ], - static_libs: [ - "junit", - "androidx.test.rules", - "mockito-target-minus-junit4", - "truth-prebuilt", - ], -} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadIconChangedEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadIconChangedEventTest.java deleted file mode 100644 index 6c311f99f695..000000000000 --- a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadIconChangedEventTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tests.ims; - -import static com.google.common.truth.Truth.assertThat; - -import android.net.Uri; -import android.os.Parcel; -import android.telephony.ims.RcsGroupThreadIconChangedEvent; -import android.telephony.ims.RcsGroupThreadIconChangedEventDescriptor; - -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class RcsGroupThreadIconChangedEventTest { - - @Test - public void testCanUnparcel() { - int rcsGroupThreadId = 1; - int rcsParticipantId = 2; - Uri newIconUri = Uri.parse("content://new_icon"); - - RcsGroupThreadIconChangedEventDescriptor iconChangedEventDescriptor = - new RcsGroupThreadIconChangedEventDescriptor(1234567890, rcsGroupThreadId, - rcsParticipantId, newIconUri); - - Parcel parcel = Parcel.obtain(); - iconChangedEventDescriptor.writeToParcel( - parcel, iconChangedEventDescriptor.describeContents()); - - parcel.setDataPosition(0); - - iconChangedEventDescriptor = - RcsGroupThreadIconChangedEventDescriptor.CREATOR.createFromParcel(parcel); - - RcsGroupThreadIconChangedEvent iconChangedEvent = - iconChangedEventDescriptor.createRcsEvent(null); - - assertThat(iconChangedEvent.getNewIcon()).isEqualTo(newIconUri); - assertThat(iconChangedEvent.getRcsGroupThread().getThreadId()).isEqualTo(1); - assertThat(iconChangedEvent.getOriginatingParticipant().getId()).isEqualTo(2); - assertThat(iconChangedEvent.getTimestamp()).isEqualTo(1234567890); - } -} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadNameChangedEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadNameChangedEventTest.java deleted file mode 100644 index a60dabb023f8..000000000000 --- a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadNameChangedEventTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tests.ims; - -import static com.google.common.truth.Truth.assertThat; - -import android.os.Parcel; -import android.telephony.ims.RcsGroupThreadNameChangedEvent; -import android.telephony.ims.RcsGroupThreadNameChangedEventDescriptor; - -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class RcsGroupThreadNameChangedEventTest { - @Test - public void testCanUnparcel() { - String newName = "new name"; - - int rcsGroupThreadId = 1; - int rcsParticipantId = 2; - - RcsGroupThreadNameChangedEventDescriptor nameChangedEventDescriptor = - new RcsGroupThreadNameChangedEventDescriptor( - 1234567890, rcsGroupThreadId, rcsParticipantId, newName); - - Parcel parcel = Parcel.obtain(); - nameChangedEventDescriptor.writeToParcel( - parcel, nameChangedEventDescriptor.describeContents()); - - parcel.setDataPosition(0); - - nameChangedEventDescriptor = RcsGroupThreadNameChangedEventDescriptor.CREATOR - .createFromParcel(parcel); - - RcsGroupThreadNameChangedEvent nameChangedEvent = - nameChangedEventDescriptor.createRcsEvent(null); - - assertThat(nameChangedEvent.getNewName()).isEqualTo(newName); - assertThat(nameChangedEvent.getRcsGroupThread().getThreadId()).isEqualTo(1); - assertThat(nameChangedEvent.getOriginatingParticipant().getId()).isEqualTo(2); - assertThat(nameChangedEvent.getTimestamp()).isEqualTo(1234567890); - } -} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantJoinedEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantJoinedEventTest.java deleted file mode 100644 index 7b02cb1eaafa..000000000000 --- a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantJoinedEventTest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tests.ims; - -import static com.google.common.truth.Truth.assertThat; - -import android.os.Parcel; -import android.telephony.ims.RcsGroupThreadParticipantJoinedEvent; -import android.telephony.ims.RcsGroupThreadParticipantJoinedEventDescriptor; - -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class RcsGroupThreadParticipantJoinedEventTest { - - @Test - public void testCanUnparcel() { - int rcsGroupThreadId = 1; - int rcsParticipantId = 2; - - RcsGroupThreadParticipantJoinedEventDescriptor participantJoinedEventDescriptor = - new RcsGroupThreadParticipantJoinedEventDescriptor( - 1234567890, rcsGroupThreadId, rcsParticipantId, rcsParticipantId); - - Parcel parcel = Parcel.obtain(); - participantJoinedEventDescriptor.writeToParcel( - parcel, participantJoinedEventDescriptor.describeContents()); - - parcel.setDataPosition(0); - - participantJoinedEventDescriptor = RcsGroupThreadParticipantJoinedEventDescriptor.CREATOR - .createFromParcel(parcel); - - RcsGroupThreadParticipantJoinedEvent participantJoinedEvent = - participantJoinedEventDescriptor.createRcsEvent(null); - - assertThat(participantJoinedEvent.getJoinedParticipant().getId()).isEqualTo(2); - assertThat(participantJoinedEvent.getRcsGroupThread().getThreadId()).isEqualTo(1); - assertThat(participantJoinedEvent.getOriginatingParticipant().getId()).isEqualTo(2); - assertThat(participantJoinedEvent.getTimestamp()).isEqualTo(1234567890); - } -} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantLeftEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantLeftEventTest.java deleted file mode 100644 index 51466bd98d60..000000000000 --- a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantLeftEventTest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tests.ims; - -import static com.google.common.truth.Truth.assertThat; - -import android.os.Parcel; -import android.telephony.ims.RcsGroupThreadParticipantLeftEvent; -import android.telephony.ims.RcsGroupThreadParticipantLeftEventDescriptor; - -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class RcsGroupThreadParticipantLeftEventTest { - @Test - public void testCanUnparcel() { - int rcsGroupThreadId = 1; - int rcsParticipantId = 2; - - RcsGroupThreadParticipantLeftEventDescriptor participantLeftEventDescriptor = - new RcsGroupThreadParticipantLeftEventDescriptor( - 1234567890, rcsGroupThreadId, rcsParticipantId, rcsParticipantId); - - Parcel parcel = Parcel.obtain(); - participantLeftEventDescriptor.writeToParcel( - parcel, participantLeftEventDescriptor.describeContents()); - - parcel.setDataPosition(0); - - // create from parcel - parcel.setDataPosition(0); - participantLeftEventDescriptor = RcsGroupThreadParticipantLeftEventDescriptor.CREATOR - .createFromParcel(parcel); - - RcsGroupThreadParticipantLeftEvent participantLeftEvent = - participantLeftEventDescriptor.createRcsEvent(null); - - assertThat(participantLeftEvent.getRcsGroupThread().getThreadId()).isEqualTo(1); - assertThat(participantLeftEvent.getLeavingParticipant().getId()).isEqualTo(2); - assertThat(participantLeftEvent.getTimestamp()).isEqualTo(1234567890); - } -} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsParticipantAliasChangedEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsParticipantAliasChangedEventTest.java deleted file mode 100644 index 56830dff1ce5..000000000000 --- a/tests/RcsTests/src/com/android/tests/ims/RcsParticipantAliasChangedEventTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tests.ims; - -import static com.google.common.truth.Truth.assertThat; - -import android.os.Parcel; -import android.telephony.ims.RcsParticipantAliasChangedEvent; -import android.telephony.ims.RcsParticipantAliasChangedEventDescriptor; - -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class RcsParticipantAliasChangedEventTest { - private static final String OLD_ALIAS = "old alias"; - private static final String NEW_ALIAS = "new alias"; - private int mParticipantId = 3; - - @Test - public void testCanUnparcel() { - RcsParticipantAliasChangedEventDescriptor aliasChangedEventDescriptor = - new RcsParticipantAliasChangedEventDescriptor( - 1234567890, mParticipantId, NEW_ALIAS); - - Parcel parcel = Parcel.obtain(); - aliasChangedEventDescriptor.writeToParcel( - parcel, aliasChangedEventDescriptor.describeContents()); - - parcel.setDataPosition(0); - - aliasChangedEventDescriptor = RcsParticipantAliasChangedEventDescriptor.CREATOR - .createFromParcel(parcel); - - RcsParticipantAliasChangedEvent aliasChangedEvent = - aliasChangedEventDescriptor.createRcsEvent(null); - - assertThat(aliasChangedEvent.getParticipant().getId()).isEqualTo(mParticipantId); - assertThat(aliasChangedEvent.getNewAlias()).isEqualTo(NEW_ALIAS); - assertThat(aliasChangedEvent.getTimestamp()).isEqualTo(1234567890); - } -} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsParticipantQueryParamsTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsParticipantQueryParamsTest.java deleted file mode 100644 index 2d95513be069..000000000000 --- a/tests/RcsTests/src/com/android/tests/ims/RcsParticipantQueryParamsTest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tests.ims; - -import static com.google.common.truth.Truth.assertThat; - -import android.os.Parcel; -import android.telephony.ims.RcsParticipantQueryParams; - -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class RcsParticipantQueryParamsTest { - - @Test - public void testCanUnparcel() { - RcsParticipantQueryParams rcsParticipantQueryParams = - new RcsParticipantQueryParams.Builder() - .setAliasLike("%alias_") - .setCanonicalAddressLike("_canonical%") - .setSortProperty(RcsParticipantQueryParams.SORT_BY_CANONICAL_ADDRESS) - .setSortDirection(true) - .setResultLimit(432) - .build(); - - - Parcel parcel = Parcel.obtain(); - rcsParticipantQueryParams.writeToParcel(parcel, - rcsParticipantQueryParams.describeContents()); - - parcel.setDataPosition(0); - rcsParticipantQueryParams = RcsParticipantQueryParams.CREATOR.createFromParcel( - parcel); - - assertThat(rcsParticipantQueryParams.getAliasLike()).isEqualTo("%alias_"); - assertThat(rcsParticipantQueryParams.getCanonicalAddressLike()).contains("_canonical%"); - assertThat(rcsParticipantQueryParams.getLimit()).isEqualTo(432); - assertThat(rcsParticipantQueryParams.getSortingProperty()).isEqualTo( - RcsParticipantQueryParams.SORT_BY_CANONICAL_ADDRESS); - assertThat(rcsParticipantQueryParams.getSortDirection()).isTrue(); - } -} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParamsTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParamsTest.java deleted file mode 100644 index 7a3e384020a1..000000000000 --- a/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParamsTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tests.ims; - -import static android.telephony.ims.RcsThreadQueryParams.SORT_BY_TIMESTAMP; -import static android.telephony.ims.RcsThreadQueryParams.THREAD_TYPE_GROUP; - -import static com.google.common.truth.Truth.assertThat; - -import android.os.Parcel; -import android.telephony.ims.RcsParticipant; -import android.telephony.ims.RcsThreadQueryParams; - -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class RcsThreadQueryParamsTest { - - @Test - public void testCanUnparcel() { - RcsParticipant rcsParticipant = new RcsParticipant(null, 1); - RcsThreadQueryParams rcsThreadQueryParams = new RcsThreadQueryParams.Builder() - .setThreadType(THREAD_TYPE_GROUP) - .setParticipant(rcsParticipant) - .setResultLimit(50) - .setSortProperty(SORT_BY_TIMESTAMP) - .setSortDirection(true) - .build(); - - Parcel parcel = Parcel.obtain(); - rcsThreadQueryParams.writeToParcel(parcel, rcsThreadQueryParams.describeContents()); - - parcel.setDataPosition(0); - rcsThreadQueryParams = RcsThreadQueryParams.CREATOR.createFromParcel(parcel); - - assertThat(rcsThreadQueryParams.getThreadType()).isEqualTo(THREAD_TYPE_GROUP); - assertThat(rcsThreadQueryParams.getRcsParticipantsIds()) - .contains(rcsParticipant.getId()); - assertThat(rcsThreadQueryParams.getLimit()).isEqualTo(50); - assertThat(rcsThreadQueryParams.getSortingProperty()).isEqualTo(SORT_BY_TIMESTAMP); - assertThat(rcsThreadQueryParams.getSortDirection()).isTrue(); - } -} diff --git a/tests/RenderThreadTest/res/drawable-xhdpi/starry_night_bg.jpg b/tests/RenderThreadTest/res/drawable-xhdpi/starry_night_bg.jpg Binary files differindex 755232deea0e..14d6027bf006 100644 --- a/tests/RenderThreadTest/res/drawable-xhdpi/starry_night_bg.jpg +++ b/tests/RenderThreadTest/res/drawable-xhdpi/starry_night_bg.jpg diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp index aec40558ad51..a23df920b396 100644 --- a/tests/RollbackTest/Android.bp +++ b/tests/RollbackTest/Android.bp @@ -12,96 +12,104 @@ // See the License for the specific language governing permissions and // limitations under the License. -android_test_helper_app { - name: "RollbackTestAppAv1", - manifest: "TestApp/Av1.xml", - sdk_version: "current", - srcs: ["TestApp/src/**/*.java"], - resource_dirs: ["TestApp/res_v1"], +android_test { + name: "RollbackTest", + manifest: "RollbackTest/AndroidManifest.xml", + srcs: ["RollbackTest/src/**/*.java"], + static_libs: ["androidx.test.rules", "cts-rollback-lib", "cts-install-lib"], + test_suites: ["general-tests"], + test_config: "RollbackTest.xml", + java_resources: [ + ":com.android.apex.apkrollback.test_v2", + ":com.android.apex.apkrollback.test_v2Crashing" + ], } -android_test_helper_app { - name: "RollbackTestAppAv2", - manifest: "TestApp/Av2.xml", - sdk_version: "current", - srcs: ["TestApp/src/**/*.java"], - resource_dirs: ["TestApp/res_v2"], +java_test_host { + name: "StagedRollbackTest", + srcs: ["StagedRollbackTest/src/**/*.java"], + libs: ["tradefed"], + static_libs: [ + "compatibility-tradefed", + "frameworks-base-hostutils", + "RollbackTestLib", + "testng", + ], + test_suites: ["general-tests"], + test_config: "StagedRollbackTest.xml", + data: [":com.android.apex.apkrollback.test_v1"], } -android_test_helper_app { - name: "RollbackTestAppAv3", - manifest: "TestApp/Av3.xml", - sdk_version: "current", - srcs: ["TestApp/src/**/*.java"], - resource_dirs: ["TestApp/res_v3"], +java_test_host { + name: "NetworkStagedRollbackTest", + srcs: ["NetworkStagedRollbackTest/src/**/*.java"], + libs: ["tradefed"], + static_libs: ["RollbackTestLib", "frameworks-base-hostutils"], + test_suites: ["general-tests"], + test_config: "NetworkStagedRollbackTest.xml", } -android_test_helper_app { - name: "RollbackTestAppACrashingV2", - manifest: "TestApp/ACrashingV2.xml", - sdk_version: "current", - srcs: ["TestApp/src/**/*.java"], - resource_dirs: ["TestApp/res_v2"], +java_test_host { + name: "MultiUserRollbackTest", + srcs: ["MultiUserRollbackTest/src/**/*.java"], + libs: ["tradefed"], + test_suites: ["general-tests"], + test_config: "MultiUserRollbackTest.xml", } -android_test_helper_app { - name: "RollbackTestAppBv1", - manifest: "TestApp/Bv1.xml", - sdk_version: "current", - srcs: ["TestApp/src/**/*.java"], - resource_dirs: ["TestApp/res_v1"], +java_library_host { + name: "RollbackTestLib", + srcs: ["lib/src/**/*.java"], + libs: ["tradefed"], } -android_test_helper_app { - name: "RollbackTestAppBv2", - manifest: "TestApp/Bv2.xml", - sdk_version: "current", - srcs: ["TestApp/src/**/*.java"], - resource_dirs: ["TestApp/res_v2"], +genrule { + name: "com.android.apex.apkrollback.test.pem", + out: ["com.android.apex.apkrollback.test.pem"], + cmd: "openssl genrsa -out $(out) 4096", } -android_test_helper_app { - name: "RollbackTestAppASplitV1", - manifest: "TestApp/Av1.xml", - sdk_version: "current", - srcs: ["TestApp/src/**/*.java"], - resource_dirs: ["TestApp/res_v1"], - package_splits: ["anydpi"], +genrule { + name: "com.android.apex.apkrollback.test.pubkey", + srcs: [":com.android.apex.apkrollback.test.pem"], + out: ["com.android.apex.apkrollback.test.pubkey"], + tools: ["avbtool"], + cmd: "$(location avbtool) extract_public_key --key $(in) --output $(out)", } -android_test_helper_app { - name: "RollbackTestAppASplitV2", - manifest: "TestApp/Av2.xml", - sdk_version: "current", - srcs: ["TestApp/src/**/*.java"], - resource_dirs: ["TestApp/res_v2"], - package_splits: ["anydpi"], +apex_key { + name: "com.android.apex.apkrollback.test.key", + private_key: ":com.android.apex.apkrollback.test.pem", + public_key: ":com.android.apex.apkrollback.test.pubkey", + installable: false, } -android_test { - name: "RollbackTest", - manifest: "RollbackTest/AndroidManifest.xml", - srcs: ["RollbackTest/src/**/*.java"], - static_libs: ["androidx.test.rules"], - test_suites: ["general-tests"], - java_resources: [ - ":RollbackTestAppAv1", - ":RollbackTestAppAv2", - ":RollbackTestAppAv3", - ":RollbackTestAppACrashingV2", - ":RollbackTestAppBv1", - ":RollbackTestAppBv2", - ":RollbackTestAppASplitV1", - ":RollbackTestAppASplitV2", - ], - test_config: "RollbackTest.xml", - // TODO: sdk_version: "test_current" when Intent#resolveSystemservice is TestApi +apex { + name: "com.android.apex.apkrollback.test_v1", + manifest: "testdata/manifest_v1.json", + androidManifest: "testdata/AndroidManifest.xml", + file_contexts: ":apex.test-file_contexts", + key: "com.android.apex.apkrollback.test.key", + apps: ["TestAppAv1"], + installable: false, } -java_test_host { - name: "StagedRollbackTest", - srcs: ["StagedRollbackTest/src/**/*.java"], - libs: ["tradefed"], - test_suites: ["general-tests"], - test_config: "StagedRollbackTest.xml", +apex { + name: "com.android.apex.apkrollback.test_v2", + manifest: "testdata/manifest_v2.json", + androidManifest: "testdata/AndroidManifest.xml", + file_contexts: ":apex.test-file_contexts", + key: "com.android.apex.apkrollback.test.key", + apps: ["TestAppAv2"], + installable: false, } + +apex { + name: "com.android.apex.apkrollback.test_v2Crashing", + manifest: "testdata/manifest_v2.json", + androidManifest: "testdata/AndroidManifest.xml", + file_contexts: ":apex.test-file_contexts", + key: "com.android.apex.apkrollback.test.key", + apps: ["TestAppACrashingV2"], + installable: false, +}
\ No newline at end of file diff --git a/tests/RollbackTest/MultiUserRollbackTest.xml b/tests/RollbackTest/MultiUserRollbackTest.xml new file mode 100644 index 000000000000..2f62af1856da --- /dev/null +++ b/tests/RollbackTest/MultiUserRollbackTest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs rollback tests for multiple users"> + <option name="test-suite-tag" value="MultiUserRollbackTest" /> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" /> + <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" /> + <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" /> + <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" /> + </target_preparer> + <test class="com.android.tradefed.testtype.HostTest" > + <option name="class" value="com.android.tests.rollback.host.MultiUserRollbackTest" /> + </test> +</configuration> diff --git a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java new file mode 100644 index 000000000000..42b886f0774f --- /dev/null +++ b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tests.rollback.host; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.TimeUnit; + +/** + * Runs rollback tests for multiple users. + */ +@RunWith(DeviceJUnit4ClassRunner.class) +public class MultiUserRollbackTest extends BaseHostJUnit4Test { + // The user that was running originally when the test starts. + private int mOriginalUserId; + private int mSecondaryUserId = -1; + private static final long SWITCH_USER_COMPLETED_NUMBER_OF_POLLS = 60; + private static final long SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS = 1000; + + @After + public void tearDown() throws Exception { + removeSecondaryUserIfNecessary(); + runPhaseForUsers("cleanUp", mOriginalUserId); + } + + @Before + public void setup() throws Exception { + mOriginalUserId = getDevice().getCurrentUser(); + createAndStartSecondaryUser(); + installPackage("RollbackTest.apk", "--user all"); + runPhaseForUsers("cleanUp", mOriginalUserId); + } + + @Test + public void testBasicForSecondaryUser() throws Exception { + runPhaseForUsers("testBasic", mSecondaryUserId); + } + + @Test + public void testMultipleUsers() throws Exception { + runPhaseForUsers("testMultipleUsersInstallV1", mOriginalUserId, mSecondaryUserId); + runPhaseForUsers("testMultipleUsersUpgradeToV2", mOriginalUserId); + runPhaseForUsers("testMultipleUsersUpdateUserData", mOriginalUserId, mSecondaryUserId); + getDevice().executeShellCommand("pm rollback-app com.android.cts.install.lib.testapp.A"); + runPhaseForUsers("testMultipleUsersVerifyUserdataRollback", mOriginalUserId, + mSecondaryUserId); + } + + /** + * Run the phase for the given user ids, in the order they are given. + */ + private void runPhaseForUsers(String phase, int... userIds) throws Exception { + final long timeout = TimeUnit.MINUTES.toMillis(10); + for (int userId: userIds) { + assertTrue(runDeviceTests(getDevice(), "com.android.tests.rollback", + "com.android.tests.rollback.MultiUserRollbackTest", + phase, userId, timeout)); + } + } + + private void removeSecondaryUserIfNecessary() throws Exception { + if (mSecondaryUserId != -1) { + getDevice().removeUser(mSecondaryUserId); + mSecondaryUserId = -1; + } + } + + private void awaitUserUnlocked(int userId) throws Exception { + for (int i = 0; i < SWITCH_USER_COMPLETED_NUMBER_OF_POLLS; ++i) { + String userState = getDevice().executeShellCommand("am get-started-user-state " + + userId); + if (userState.contains("RUNNING_UNLOCKED")) { + return; + } + Thread.sleep(SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS); + } + fail("Timed out in unlocking user: " + userId); + } + + private void createAndStartSecondaryUser() throws Exception { + String name = "MultiUserRollbackTest_User" + System.currentTimeMillis(); + mSecondaryUserId = getDevice().createUser(name); + getDevice().startUser(mSecondaryUserId); + // Note we can't install apps on a locked user + awaitUserUnlocked(mSecondaryUserId); + } +} diff --git a/tests/RollbackTest/NetworkStagedRollbackTest.xml b/tests/RollbackTest/NetworkStagedRollbackTest.xml new file mode 100644 index 000000000000..2ab907a59298 --- /dev/null +++ b/tests/RollbackTest/NetworkStagedRollbackTest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs the network staged rollback tests"> + <option name="test-suite-tag" value="NetworkStagedRollbackTest" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="RollbackTest.apk" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="am broadcast -a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' --es package "com.google.android.gms.platformconfigurator" --es user '\\*' --esa flags "ModuleConfig__immediate_commit_packages" --esa types "bytes" --esa values "CgA=" com.google.android.gms" /> + <option name="run-command" value="am broadcast -a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' --es package "com.google.android.gms.platformconfigurator" --es user '\\*' --esa flags "ModuleConfig__versioned_immediate_commit_packages" --esa types "bytes" --esa values "Cm5vdGFwYWNrYWdlOgA=" com.google.android.gms" /> + <option name="teardown-command" value="am broadcast -a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' --es action delete --es package "com.google.android.gms.platformconfigurator" --es user '\*' --esa flag "ModuleConfig__immediate_commit_packages" com.google.android.gms" /> + <option name="teardown-command" value="am broadcast -a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' --es action delete --es package "com.google.android.gms.platformconfigurator" --es user '\*' --esa flag "ModuleConfig__versioned_immediate_commit_packages" com.google.android.gms" /> + </target_preparer> + <test class="com.android.tradefed.testtype.HostTest" > + <option name="class" value="com.android.tests.rollback.host.NetworkStagedRollbackTest" /> + </test> +</configuration> diff --git a/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java new file mode 100644 index 000000000000..e3c7cfaf3bef --- /dev/null +++ b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tests.rollback.host; + +import static com.android.tests.rollback.host.WatchdogEventLogger.watchdogEventOccurred; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Runs the network rollback tests. + */ +@RunWith(DeviceJUnit4ClassRunner.class) +public class NetworkStagedRollbackTest extends BaseHostJUnit4Test { + /** + * Runs the given phase of a test by calling into the device. + * Throws an exception if the test phase fails. + * <p> + * For example, <code>runPhase("testApkOnlyEnableRollback");</code> + */ + private void runPhase(String phase) throws Exception { + assertTrue(runDeviceTests("com.android.tests.rollback", + "com.android.tests.rollback.NetworkStagedRollbackTest", + phase)); + } + + private static final String REASON_EXPLICIT_HEALTH_CHECK = "REASON_EXPLICIT_HEALTH_CHECK"; + + private static final String ROLLBACK_INITIATE = "ROLLBACK_INITIATE"; + private static final String ROLLBACK_BOOT_TRIGGERED = "ROLLBACK_BOOT_TRIGGERED"; + private static final String ROLLBACK_SUCCESS = "ROLLBACK_SUCCESS"; + + private WatchdogEventLogger mLogger = new WatchdogEventLogger(); + + @Before + public void setUp() throws Exception { + runPhase("cleanUp"); + mLogger.start(getDevice()); + } + + @After + public void tearDown() throws Exception { + mLogger.stop(); + runPhase("cleanUp"); + } + + /** + * Tests failed network health check triggers watchdog staged rollbacks. + */ + @Ignore("b/159569441") + @Test + public void testNetworkFailedRollback() throws Exception { + try { + // Disconnect internet so we can test network health triggered rollbacks + getDevice().executeShellCommand("svc wifi disable"); + getDevice().executeShellCommand("svc data disable"); + + runPhase("testNetworkFailedRollback_Phase1"); + // Reboot device to activate staged package + getDevice().reboot(); + + // Verify rollback was enabled + runPhase("testNetworkFailedRollback_Phase2"); + // Wait for reboot to happen + assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5))); + // Wait for reboot to complete and device to become available + getDevice().waitForDeviceAvailable(); + // Verify rollback was executed after health check deadline + runPhase("testNetworkFailedRollback_Phase3"); + + List<String> watchdogEvents = mLogger.getWatchdogLoggingEvents(); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null, + REASON_EXPLICIT_HEALTH_CHECK, null)); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null, + null, null)); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_SUCCESS, null, null, null)); + } finally { + // Reconnect internet again so we won't break tests which assume internet available + getDevice().executeShellCommand("svc wifi enable"); + getDevice().executeShellCommand("svc data enable"); + } + } + + /** + * Tests passed network health check does not trigger watchdog staged rollbacks. + */ + @Test + public void testNetworkPassedDoesNotRollback() throws Exception { + runPhase("testNetworkPassedDoesNotRollback_Phase1"); + // Reboot device to activate staged package + getDevice().reboot(); + + // Verify rollback was enabled + runPhase("testNetworkPassedDoesNotRollback_Phase2"); + + // Connect to internet so network health check passes + getDevice().executeShellCommand("svc wifi enable"); + getDevice().executeShellCommand("svc data enable"); + + // Wait for device available because emulator device may restart after turning + // on mobile data + getDevice().waitForDeviceAvailable(); + + // Verify rollback was not executed after health check deadline + runPhase("testNetworkPassedDoesNotRollback_Phase3"); + + List<String> watchdogEvents = mLogger.getWatchdogLoggingEvents(); + assertEquals(watchdogEventOccurred(watchdogEvents, null, null, + REASON_EXPLICIT_HEALTH_CHECK, null), false); + } +} diff --git a/tests/RollbackTest/RollbackTest.xml b/tests/RollbackTest/RollbackTest.xml index 70cd86783d6d..7b85cc84f1f5 100644 --- a/tests/RollbackTest/RollbackTest.xml +++ b/tests/RollbackTest/RollbackTest.xml @@ -18,12 +18,23 @@ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> <option name="test-file-name" value="RollbackTest.apk" /> </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="am broadcast -a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' --es package "com.google.android.gms.platformconfigurator" --es user '\\*' --esa flags "ModuleConfig__immediate_commit_packages" --esa types "bytes" --esa values "CgA=" com.google.android.gms" /> + <option name="run-command" value="am broadcast -a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' --es package "com.google.android.gms.platformconfigurator" --es user '\\*' --esa flags "ModuleConfig__versioned_immediate_commit_packages" --esa types "bytes" --esa values "Cm5vdGFwYWNrYWdlOgA=" com.google.android.gms" /> + <option name="teardown-command" value="am broadcast -a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' --es action delete --es package "com.google.android.gms.platformconfigurator" --es user '\*' --esa flag "ModuleConfig__immediate_commit_packages" com.google.android.gms" /> + <option name="teardown-command" value="am broadcast -a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' --es action delete --es package "com.google.android.gms.platformconfigurator" --es user '\*' --esa flag "ModuleConfig__versioned_immediate_commit_packages" com.google.android.gms" /> + <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" /> + <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" /> + <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" /> + <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" /> + </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="com.android.tests.rollback" /> <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> - <!-- Exclude the StagedRollbackTest tests, which needs to be specially - driven from the StagedRollbackTest host test --> + <!-- Exclude the device tests which need to be specially driven from the host tests --> <option name="exclude-filter" value="com.android.tests.rollback.StagedRollbackTest" /> + <option name="exclude-filter" value="com.android.tests.rollback.NetworkStagedRollbackTest" /> + <option name="exclude-filter" value="com.android.tests.rollback.MultiUserRollbackTest" /> </test> </configuration> diff --git a/tests/RollbackTest/RollbackTest/AndroidManifest.xml b/tests/RollbackTest/RollbackTest/AndroidManifest.xml index 5380dc9fc8cd..9274da268735 100644 --- a/tests/RollbackTest/RollbackTest/AndroidManifest.xml +++ b/tests/RollbackTest/RollbackTest/AndroidManifest.xml @@ -17,8 +17,9 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.tests.rollback" > + <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> <application> - <receiver android:name="com.android.tests.rollback.LocalIntentSender" + <receiver android:name="com.android.cts.install.lib.LocalIntentSender" android:exported="true" /> <uses-library android:name="android.test.runner" /> </application> diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/LocalIntentSender.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/LocalIntentSender.java deleted file mode 100644 index 267ef7377b36..000000000000 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/LocalIntentSender.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tests.rollback; - -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentSender; - -import androidx.test.InstrumentationRegistry; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; - -/** - * Make IntentSender that sends intent locally. - */ -public class LocalIntentSender extends BroadcastReceiver { - - private static final String TAG = "RollbackTest"; - - private static final BlockingQueue<Intent> sIntentSenderResults = new LinkedBlockingQueue<>(); - - @Override - public void onReceive(Context context, Intent intent) { - sIntentSenderResults.add(intent); - } - - /** - * Get a LocalIntentSender. - */ - static IntentSender getIntentSender() { - Context context = InstrumentationRegistry.getContext(); - Intent intent = new Intent(context, LocalIntentSender.class); - PendingIntent pending = PendingIntent.getBroadcast(context, 0, intent, 0); - return pending.getIntentSender(); - } - - /** - * Returns the most recent Intent sent by a LocalIntentSender. - */ - static Intent getIntentSenderResult() throws InterruptedException { - return sIntentSenderResults.take(); - } -} diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java new file mode 100644 index 000000000000..8641f4d4013a --- /dev/null +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tests.rollback; + +import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat; + +import static com.google.common.truth.Truth.assertThat; + +import android.Manifest; +import android.content.rollback.RollbackInfo; +import android.content.rollback.RollbackManager; + +import com.android.cts.install.lib.Install; +import com.android.cts.install.lib.InstallUtils; +import com.android.cts.install.lib.TestApp; +import com.android.cts.rollback.lib.Rollback; +import com.android.cts.rollback.lib.RollbackUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + + +@RunWith(JUnit4.class) +public class MultiUserRollbackTest { + + @Before + public void adoptShellPermissions() { + InstallUtils.adoptShellPermissionIdentity( + Manifest.permission.INSTALL_PACKAGES, + Manifest.permission.DELETE_PACKAGES, + Manifest.permission.TEST_MANAGE_ROLLBACKS, + Manifest.permission.MANAGE_ROLLBACKS); + } + + @After + public void dropShellPermissions() { + InstallUtils.dropShellPermissionIdentity(); + } + + @Test + public void cleanUp() { + RollbackManager rm = RollbackUtils.getRollbackManager(); + rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream()) + .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage); + rm.getRecentlyCommittedRollbacks().stream().flatMap(info -> info.getPackages().stream()) + .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage); + assertThat(rm.getAvailableRollbacks()).isEmpty(); + assertThat(rm.getRecentlyCommittedRollbacks()).isEmpty(); + } + + @Test + public void testBasic() throws Exception { + new RollbackTest().testBasic(); + } + + /** + * Install version 1 of the test app. This method is run for both users. + */ + @Test + public void testMultipleUsersInstallV1() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); + Install.single(TestApp.A1).commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + InstallUtils.processUserData(TestApp.A); + } + + /** + * Upgrade the test app to version 2. This method should only run once as the system user, + * and will update the app for both users. + */ + @Test + public void testMultipleUsersUpgradeToV2() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + Install.single(TestApp.A2).setEnableRollback().commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + RollbackInfo rollback = RollbackUtils.waitForAvailableRollback(TestApp.A); + assertThat(rollback).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1)); + } + + /** + * This method is run for both users. Assert that the test app has upgraded for both users, and + * update their userdata to reflect this new version. + */ + @Test + public void testMultipleUsersUpdateUserData() { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + InstallUtils.processUserData(TestApp.A); + } + + /** + * The system will have rolled back the test app at this stage. Verify that the rollback has + * taken place, and that the userdata has been correctly rolled back. This method is run for + * both users. + */ + @Test + public void testMultipleUsersVerifyUserdataRollback() { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + InstallUtils.processUserData(TestApp.A); + } +} diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java new file mode 100644 index 000000000000..314e95229d29 --- /dev/null +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tests.rollback; + +import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat; +import static com.android.cts.rollback.lib.RollbackUtils.getUniqueRollbackInfoForPackage; + +import static com.google.common.truth.Truth.assertThat; + +import android.Manifest; +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.rollback.RollbackManager; +import android.os.ParcelFileDescriptor; +import android.provider.DeviceConfig; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.cts.install.lib.Install; +import com.android.cts.install.lib.InstallUtils; +import com.android.cts.install.lib.TestApp; +import com.android.cts.rollback.lib.RollbackUtils; + +import libcore.io.IoUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.File; +import java.util.concurrent.TimeUnit; + +@RunWith(JUnit4.class) +public class NetworkStagedRollbackTest { + private static final String NETWORK_STACK_CONNECTOR_CLASS = + "android.net.INetworkStackConnector"; + private static final String PROPERTY_WATCHDOG_REQUEST_TIMEOUT_MILLIS = + "watchdog_request_timeout_millis"; + + private static final String[] NETWORK_STACK_APK_NAMES = { + "NetworkStack", "NetworkStackGoogle", "NetworkStackNext", "NetworkStackNextGoogle" + }; + + private static final TestApp NETWORK_STACK = new TestApp("NetworkStack", + getNetworkStackPackageName(), -1, false, findNetworkStackApk()); + + private static File[] findNetworkStackApk() { + for (String name : NETWORK_STACK_APK_NAMES) { + final File apk = new File("/system/priv-app/" + name + "/" + name + ".apk"); + if (apk.isFile()) { + final File dir = new File("/system/priv-app/" + name); + return dir.listFiles((d, f) -> f.startsWith(name)); + } + } + throw new RuntimeException("Can't find NetworkStackApk"); + } + + /** + * Adopts common shell permissions needed for rollback tests. + */ + @Before + public void adoptShellPermissions() { + InstallUtils.adoptShellPermissionIdentity( + Manifest.permission.INSTALL_PACKAGES, + Manifest.permission.DELETE_PACKAGES, + Manifest.permission.TEST_MANAGE_ROLLBACKS, + Manifest.permission.FORCE_STOP_PACKAGES, + Manifest.permission.WRITE_DEVICE_CONFIG); + } + + /** + * Drops shell permissions needed for rollback tests. + */ + @After + public void dropShellPermissions() { + InstallUtils.dropShellPermissionIdentity(); + } + + @Test + public void cleanUp() { + RollbackManager rm = RollbackUtils.getRollbackManager(); + rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream()) + .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage); + rm.getRecentlyCommittedRollbacks().stream().flatMap(info -> info.getPackages().stream()) + .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage); + assertThat(rm.getAvailableRollbacks()).isEmpty(); + assertThat(rm.getRecentlyCommittedRollbacks()).isEmpty(); + uninstallNetworkStackPackage(); + } + + @Test + public void testNetworkFailedRollback_Phase1() throws Exception { + // Remove available rollbacks and uninstall NetworkStack on /data/ + RollbackManager rm = RollbackUtils.getRollbackManager(); + String networkStack = getNetworkStackPackageName(); + + assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), + networkStack)).isNull(); + + // Reduce health check deadline + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, + PROPERTY_WATCHDOG_REQUEST_TIMEOUT_MILLIS, + Integer.toString(120000), false); + // Simulate re-installation of new NetworkStack with rollbacks enabled + installNetworkStackPackage(); + } + + @Test + public void testNetworkFailedRollback_Phase2() throws Exception { + RollbackManager rm = RollbackUtils.getRollbackManager(); + assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), + getNetworkStackPackageName())).isNotNull(); + + // Sleep for < health check deadline + Thread.sleep(TimeUnit.SECONDS.toMillis(5)); + // Verify rollback was not executed before health check deadline + assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), + getNetworkStackPackageName())).isNull(); + } + + @Test + public void testNetworkFailedRollback_Phase3() throws Exception { + RollbackManager rm = RollbackUtils.getRollbackManager(); + assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), + getNetworkStackPackageName())).isNotNull(); + } + + private static String getNetworkStackPackageName() { + Intent intent = new Intent(NETWORK_STACK_CONNECTOR_CLASS); + ComponentName comp = intent.resolveSystemService( + InstrumentationRegistry.getInstrumentation().getContext().getPackageManager(), 0); + return comp.getPackageName(); + } + + private static void installNetworkStackPackage() throws Exception { + Install.single(NETWORK_STACK).setStaged().setEnableRollback() + .addInstallFlags(PackageManager.INSTALL_REPLACE_EXISTING).commit(); + } + + private static void uninstallNetworkStackPackage() { + // Uninstall the package as a privileged user so we won't fail due to permission. + runShellCommand("pm uninstall " + getNetworkStackPackageName()); + } + + @Test + public void testNetworkPassedDoesNotRollback_Phase1() throws Exception { + // Remove available rollbacks and uninstall NetworkStack on /data/ + RollbackManager rm = RollbackUtils.getRollbackManager(); + String networkStack = getNetworkStackPackageName(); + + assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), + networkStack)).isNull(); + + // Reduce health check deadline, here unlike the network failed case, we use + // a longer deadline because joining a network can take a much longer time for + // reasons external to the device than 'not joining' + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, + PROPERTY_WATCHDOG_REQUEST_TIMEOUT_MILLIS, + Integer.toString(300000), false); + // Simulate re-installation of new NetworkStack with rollbacks enabled + installNetworkStackPackage(); + } + + @Test + public void testNetworkPassedDoesNotRollback_Phase2() throws Exception { + RollbackManager rm = RollbackUtils.getRollbackManager(); + assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), + getNetworkStackPackageName())).isNotNull(); + } + + @Test + public void testNetworkPassedDoesNotRollback_Phase3() throws Exception { + // Sleep for > health check deadline. We expect no rollback should happen during sleeping. + // If the device reboots for rollback, this device test will fail as well as the host test. + Thread.sleep(TimeUnit.SECONDS.toMillis(310)); + RollbackManager rm = RollbackUtils.getRollbackManager(); + assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), + getNetworkStackPackageName())).isNull(); + } + + private static void runShellCommand(String cmd) { + ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation() + .executeShellCommand(cmd); + IoUtils.closeQuietly(pfd); + } +} diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java deleted file mode 100644 index ebe54187ddb6..000000000000 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tests.rollback; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.util.Log; - -import androidx.test.InstrumentationRegistry; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; - -/** - * A broadcast receiver that can be used to get - * ACTION_ROLLBACK_COMMITTED broadcasts. - */ -class RollbackBroadcastReceiver extends BroadcastReceiver { - - private static final String TAG = "RollbackTest"; - - private final BlockingQueue<Intent> mRollbackBroadcasts = new LinkedBlockingQueue<>(); - - /** - * Creates a RollbackBroadcastReceiver and registers it with the given - * context. - */ - RollbackBroadcastReceiver() { - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_ROLLBACK_COMMITTED); - InstrumentationRegistry.getContext().registerReceiver(this, filter); - } - - @Override - public void onReceive(Context context, Intent intent) { - Log.i(TAG, "Received rollback broadcast intent"); - mRollbackBroadcasts.add(intent); - } - - /** - * Polls for at most the given amount of time for the next rollback - * broadcast. - */ - Intent poll(long timeout, TimeUnit unit) throws InterruptedException { - return mRollbackBroadcasts.poll(timeout, unit); - } - - /** - * Waits forever for the next rollback broadcast. - */ - Intent take() throws InterruptedException { - return mRollbackBroadcasts.take(); - } - - /** - * Unregisters this broadcast receiver. - */ - void unregister() { - InstrumentationRegistry.getContext().unregisterReceiver(this); - } -} diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java index 1b002cadb07f..dd08771b220d 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java @@ -16,14 +16,14 @@ package com.android.tests.rollback; -import static com.android.tests.rollback.RollbackTestUtils.assertPackageRollbackInfoEquals; -import static com.android.tests.rollback.RollbackTestUtils.assertRollbackInfoEquals; -import static com.android.tests.rollback.RollbackTestUtils.getUniqueRollbackInfoForPackage; -import static com.android.tests.rollback.RollbackTestUtils.processUserData; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +import static com.android.cts.install.lib.InstallUtils.processUserData; +import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat; +import static com.android.cts.rollback.lib.RollbackUtils.getUniqueRollbackInfoForPackage; +import static com.android.cts.rollback.lib.RollbackUtils.waitForAvailableRollback; +import static com.android.cts.rollback.lib.RollbackUtils.waitForUnavailableRollback; + +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.fail; import android.Manifest; @@ -31,22 +31,34 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.VersionedPackage; +import android.content.pm.PackageManager; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; +import android.os.UserManager; import android.provider.DeviceConfig; -import android.provider.Settings; import android.util.Log; import androidx.test.InstrumentationRegistry; +import com.android.cts.install.lib.Install; +import com.android.cts.install.lib.InstallUtils; +import com.android.cts.install.lib.TestApp; +import com.android.cts.install.lib.Uninstall; +import com.android.cts.rollback.lib.Rollback; +import com.android.cts.rollback.lib.RollbackBroadcastReceiver; +import com.android.cts.rollback.lib.RollbackUtils; + +import org.junit.After; +import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.util.Collections; +import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; /** * Test system Rollback APIs. @@ -57,8 +69,6 @@ public class RollbackTest { private static final String TAG = "RollbackTest"; - private static final String TEST_APP_A = "com.android.tests.rollback.testapp.A"; - private static final String TEST_APP_B = "com.android.tests.rollback.testapp.B"; private static final String INSTRUMENTED_APP = "com.android.tests.rollback"; // copied from PackageManagerService#PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS @@ -67,6 +77,27 @@ public class RollbackTest { private static final String PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS = "enable_rollback_timeout"; + private static boolean hasRollbackInclude(List<RollbackInfo> rollbacks, String packageName) { + return rollbacks.stream().anyMatch( + ri -> ri.getPackages().stream().anyMatch( + pri -> packageName.equals(pri.getPackageName()))); + } + + @Before + @After + public void cleanUp() { + try { + InstallUtils.adoptShellPermissionIdentity(Manifest.permission.TEST_MANAGE_ROLLBACKS); + RollbackManager rm = RollbackUtils.getRollbackManager(); + rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream()) + .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage); + rm.getRecentlyCommittedRollbacks().stream().flatMap(info -> info.getPackages().stream()) + .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage); + } finally { + InstallUtils.dropShellPermissionIdentity(); + } + } + /** * Test basic rollbacks. */ @@ -88,78 +119,87 @@ public class RollbackTest { context.registerReceiver(enableRollbackReceiver, enableRollbackFilter); try { - RollbackTestUtils.adoptShellPermissionIdentity( + InstallUtils.adoptShellPermissionIdentity( Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES, Manifest.permission.TEST_MANAGE_ROLLBACKS, - Manifest.permission.MANAGE_ROLLBACKS); + Manifest.permission.MANAGE_ROLLBACKS, + Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS, + Manifest.permission.INTERACT_ACROSS_USERS); // Register a broadcast receiver for notification when the // rollback has been committed. RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver(); - RollbackManager rm = RollbackTestUtils.getRollbackManager(); - - // Uninstall TEST_APP_A - RollbackTestUtils.uninstall(TEST_APP_A); - assertEquals(-1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + RollbackManager rm = RollbackUtils.getRollbackManager(); - // TODO: There is currently a race condition between when the app is - // uninstalled and when rollback manager deletes the rollback. Fix it - // so that's not the case! + // Uninstall TestApp.A + Uninstall.packages(TestApp.A); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); for (int i = 0; i < 5; ++i) { - RollbackInfo rollback = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TEST_APP_A); - if (rollback != null) { + if (hasRollbackInclude(rm.getRecentlyCommittedRollbacks(), TestApp.A)) { Log.i(TAG, "Sleeping 1 second to wait for uninstall to take effect."); Thread.sleep(1000); } } + assertThat(hasRollbackInclude(rm.getRecentlyCommittedRollbacks(), TestApp.A)).isFalse(); // The app should not be available for rollback. - assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A)); + waitForUnavailableRollback(TestApp.A); // There should be no recently committed rollbacks for this package. - assertNull(getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TEST_APP_A)); + assertThat(getUniqueRollbackInfoForPackage( + rm.getRecentlyCommittedRollbacks(), TestApp.A)).isNull(); // Install v1 of the app (without rollbacks enabled). - RollbackTestUtils.install("RollbackTestAppAv1.apk", false); - assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + Install.single(TestApp.A1).commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); // Upgrade from v1 to v2, with rollbacks enabled. - RollbackTestUtils.install("RollbackTestAppAv2.apk", true); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + Install.single(TestApp.A2).setEnableRollback().commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); // The app should now be available for rollback. - RollbackInfo rollback = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_A); - assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback); + RollbackInfo available = waitForAvailableRollback(TestApp.A); + assertThat(available).isNotStaged(); + assertThat(available).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1)); // We should not have received any rollback requests yet. // TODO: Possibly flaky if, by chance, some other app on device // happens to be rolled back at the same time? - assertNull(broadcastReceiver.poll(0, TimeUnit.SECONDS)); + assertThat(broadcastReceiver.poll(0, TimeUnit.SECONDS)).isNull(); // Roll back the app. - RollbackTestUtils.rollback(rollback.getRollbackId()); - assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + RollbackUtils.rollback(available.getRollbackId()); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + + UserManager um = (UserManager) context.getSystemService(context.USER_SERVICE); + List<Integer> userIds = um.getUsers(true) + .stream().map(user -> user.id).collect(Collectors.toList()); + assertThat(InstallUtils.isOnlyInstalledForUser(TestApp.A, + context.getUserId(), userIds)).isTrue(); // Verify we received a broadcast for the rollback. // TODO: Race condition between the timeout and when the broadcast is // received could lead to test flakiness. Intent broadcast = broadcastReceiver.poll(5, TimeUnit.SECONDS); - assertNotNull(broadcast); - assertNull(broadcastReceiver.poll(0, TimeUnit.SECONDS)); + assertThat(broadcast).isNotNull(); + assertThat(broadcastReceiver.poll(0, TimeUnit.SECONDS)).isNull(); // Verify the recent rollback has been recorded. - rollback = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TEST_APP_A); - assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback); + RollbackInfo committed = getUniqueRollbackInfoForPackage( + rm.getRecentlyCommittedRollbacks(), TestApp.A); + assertThat(committed).isNotNull(); + assertThat(committed).isNotStaged(); + assertThat(committed).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1)); + assertThat(committed).hasRollbackId(available.getRollbackId()); broadcastReceiver.unregister(); context.unregisterReceiver(enableRollbackReceiver); } finally { - RollbackTestUtils.dropShellPermissionIdentity(); + InstallUtils.dropShellPermissionIdentity(); } } @@ -169,50 +209,54 @@ public class RollbackTest { @Test public void testAvailableRollbackPersistence() throws Exception { try { - RollbackTestUtils.adoptShellPermissionIdentity( + InstallUtils.adoptShellPermissionIdentity( Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES, Manifest.permission.TEST_MANAGE_ROLLBACKS); - RollbackManager rm = RollbackTestUtils.getRollbackManager(); + RollbackManager rm = RollbackUtils.getRollbackManager(); - RollbackTestUtils.uninstall(TEST_APP_A); - RollbackTestUtils.install("RollbackTestAppAv1.apk", false); - RollbackTestUtils.install("RollbackTestAppAv2.apk", true); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + Uninstall.packages(TestApp.A); + Install.single(TestApp.A1).commit(); + Install.single(TestApp.A2).setEnableRollback().commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - RollbackTestUtils.uninstall(TEST_APP_B); - RollbackTestUtils.install("RollbackTestAppBv1.apk", false); - RollbackTestUtils.install("RollbackTestAppBv2.apk", true); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); + Uninstall.packages(TestApp.B); + Install.single(TestApp.B1).commit(); + Install.single(TestApp.B2).setEnableRollback().commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); // Both test apps should now be available for rollback. - RollbackInfo rollbackA = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_A); - assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA); + RollbackInfo rollbackA = waitForAvailableRollback(TestApp.A); + assertThat(rollbackA).isNotNull(); + assertThat(rollbackA).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1)); - RollbackInfo rollbackB = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_B); - assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB); + RollbackInfo rollbackB = waitForAvailableRollback(TestApp.B); + assertThat(rollbackB).isNotNull(); + assertThat(rollbackB).packagesContainsExactly( + Rollback.from(TestApp.B2).to(TestApp.B1)); // Reload the persisted data. rm.reloadPersistedData(); // The apps should still be available for rollback. - rollbackA = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_A); - assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA); + rollbackA = waitForAvailableRollback(TestApp.A); + assertThat(rollbackA).isNotNull(); + assertThat(rollbackA).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1)); - rollbackB = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_B); - assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB); + rollbackB = waitForAvailableRollback(TestApp.B); + assertThat(rollbackB).isNotNull(); + assertThat(rollbackB).packagesContainsExactly( + Rollback.from(TestApp.B2).to(TestApp.B1)); // Rollback of B should not rollback A - RollbackTestUtils.rollback(rollbackB.getRollbackId()); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); - assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); + RollbackUtils.rollback(rollbackB.getRollbackId()); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); } finally { - RollbackTestUtils.dropShellPermissionIdentity(); + InstallUtils.dropShellPermissionIdentity(); } } @@ -222,49 +266,76 @@ public class RollbackTest { @Test public void testAvailableMultiPackageRollbackPersistence() throws Exception { try { - RollbackTestUtils.adoptShellPermissionIdentity( + InstallUtils.adoptShellPermissionIdentity( Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES, Manifest.permission.TEST_MANAGE_ROLLBACKS); - RollbackManager rm = RollbackTestUtils.getRollbackManager(); + RollbackManager rm = RollbackUtils.getRollbackManager(); - RollbackTestUtils.uninstall(TEST_APP_A); - RollbackTestUtils.uninstall(TEST_APP_B); - RollbackTestUtils.installMultiPackage(false, - "RollbackTestAppAv1.apk", - "RollbackTestAppBv1.apk"); - RollbackTestUtils.installMultiPackage(true, - "RollbackTestAppAv2.apk", - "RollbackTestAppBv2.apk"); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); + Uninstall.packages(TestApp.A, TestApp.B); + Install.multi(TestApp.A1, TestApp.B1).commit(); + Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit(); + + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); // The app should now be available for rollback. - RollbackInfo rollbackA = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_A); - assertRollbackInfoForAandB(rollbackA); + RollbackInfo availableA = waitForAvailableRollback(TestApp.A); + assertThat(availableA).isNotNull(); + assertThat(availableA).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1), + Rollback.from(TestApp.B2).to(TestApp.B1)); - RollbackInfo rollbackB = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_B); - assertRollbackInfoForAandB(rollbackB); + RollbackInfo availableB = waitForAvailableRollback(TestApp.B); + assertThat(availableB).isNotNull(); + assertThat(availableB).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1), + Rollback.from(TestApp.B2).to(TestApp.B1)); + + // Assert they're both the same rollback + assertThat(availableA).hasRollbackId(availableB.getRollbackId()); // Reload the persisted data. rm.reloadPersistedData(); // The apps should still be available for rollback. - rollbackA = getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A); - assertRollbackInfoForAandB(rollbackA); - - rollbackB = getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_B); - assertRollbackInfoForAandB(rollbackB); + availableA = waitForAvailableRollback(TestApp.A); + assertThat(availableA).isNotNull(); + assertThat(availableA).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1), + Rollback.from(TestApp.B2).to(TestApp.B1)); + + availableB = waitForAvailableRollback(TestApp.B); + assertThat(availableB).isNotNull(); + assertThat(availableB).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1), + Rollback.from(TestApp.B2).to(TestApp.B1)); // Rollback of B should rollback A as well - RollbackTestUtils.rollback(rollbackB.getRollbackId()); - assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); - assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); + RollbackUtils.rollback(availableB.getRollbackId()); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); + + RollbackInfo committedA = getUniqueRollbackInfoForPackage( + rm.getRecentlyCommittedRollbacks(), TestApp.A); + assertThat(committedA).isNotNull(); + assertThat(committedA).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1), + Rollback.from(TestApp.B2).to(TestApp.B1)); + + RollbackInfo committedB = getUniqueRollbackInfoForPackage( + rm.getRecentlyCommittedRollbacks(), TestApp.A); + assertThat(committedB).isNotNull(); + assertThat(committedB).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1), + Rollback.from(TestApp.B2).to(TestApp.B1)); + + // Assert they're both the same rollback + assertThat(committedA).hasRollbackId(committedB.getRollbackId()); + assertThat(committedA).hasRollbackId(availableA.getRollbackId()); } finally { - RollbackTestUtils.dropShellPermissionIdentity(); + InstallUtils.dropShellPermissionIdentity(); } } @@ -274,42 +345,49 @@ public class RollbackTest { @Test public void testRecentlyCommittedRollbackPersistence() throws Exception { try { - RollbackTestUtils.adoptShellPermissionIdentity( + InstallUtils.adoptShellPermissionIdentity( Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES, Manifest.permission.TEST_MANAGE_ROLLBACKS); - RollbackManager rm = RollbackTestUtils.getRollbackManager(); + RollbackManager rm = RollbackUtils.getRollbackManager(); - RollbackTestUtils.uninstall(TEST_APP_A); - RollbackTestUtils.install("RollbackTestAppAv1.apk", false); - RollbackTestUtils.install("RollbackTestAppAv2.apk", true); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + Uninstall.packages(TestApp.A); + Install.single(TestApp.A1).commit(); + Install.single(TestApp.A2).setEnableRollback().commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); // The app should now be available for rollback. - RollbackInfo rollback = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_A); + RollbackInfo available = waitForAvailableRollback(TestApp.A); + assertThat(available).isNotNull(); // Roll back the app. - VersionedPackage cause = new VersionedPackage( - "com.android.tests.rollback.testapp.Foo", 42); - RollbackTestUtils.rollback(rollback.getRollbackId(), cause); - assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + TestApp cause = new TestApp("Foo", "com.android.tests.rollback.testapp.Foo", + /*versionCode*/ 42, /*isApex*/ false); + RollbackUtils.rollback(available.getRollbackId(), cause); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); // Verify the recent rollback has been recorded. - rollback = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TEST_APP_A); - assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback, cause); + RollbackInfo committed = getUniqueRollbackInfoForPackage( + rm.getRecentlyCommittedRollbacks(), TestApp.A); + assertThat(committed).isNotNull(); + assertThat(committed).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1)); + assertThat(committed).causePackagesContainsExactly(cause); // Reload the persisted data. rm.reloadPersistedData(); // Verify the recent rollback is still recorded. - rollback = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TEST_APP_A); - assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback, cause); + committed = getUniqueRollbackInfoForPackage( + rm.getRecentlyCommittedRollbacks(), TestApp.A); + assertThat(committed).isNotNull(); + assertThat(committed).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1)); + assertThat(committed).causePackagesContainsExactly(cause); + assertThat(committed).hasRollbackId(available.getRollbackId()); } finally { - RollbackTestUtils.dropShellPermissionIdentity(); + InstallUtils.dropShellPermissionIdentity(); } } @@ -320,10 +398,10 @@ public class RollbackTest { public void testRollbackExpiresAfterLifetime() throws Exception { long expirationTime = TimeUnit.SECONDS.toMillis(30); long defaultExpirationTime = TimeUnit.HOURS.toMillis(48); - RollbackManager rm = RollbackTestUtils.getRollbackManager(); + RollbackManager rm = RollbackUtils.getRollbackManager(); try { - RollbackTestUtils.adoptShellPermissionIdentity( + InstallUtils.adoptShellPermissionIdentity( Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES, Manifest.permission.TEST_MANAGE_ROLLBACKS, @@ -333,42 +411,90 @@ public class RollbackTest { RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS, Long.toString(expirationTime), false /* makeDefault*/); - // Pull the new expiration time from DeviceConfig - rm.reloadPersistedData(); - - // Uninstall TEST_APP_A - RollbackTestUtils.uninstall(TEST_APP_A); - assertEquals(-1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + // Uninstall TestApp.A + Uninstall.packages(TestApp.A); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); // Install v1 of the app (without rollbacks enabled). - RollbackTestUtils.install("RollbackTestAppAv1.apk", false); - assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + Install.single(TestApp.A1).commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); // Upgrade from v1 to v2, with rollbacks enabled. - RollbackTestUtils.install("RollbackTestAppAv2.apk", true); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + Install.single(TestApp.A2).setEnableRollback().commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); // Check that the rollback data has not expired Thread.sleep(1000); - RollbackInfo rollback = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_A); - assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback); + RollbackInfo rollback = waitForAvailableRollback(TestApp.A); + assertThat(rollback).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1)); - // Give it a little more time, but still not the long enough to expire + // Give it a little more time, but still not long enough to expire Thread.sleep(expirationTime / 2); rollback = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_A); - assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback); + rm.getAvailableRollbacks(), TestApp.A); + assertThat(rollback).isNotNull(); + assertThat(rollback).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1)); // Check that the data has expired after the expiration time (with a buffer of 1 second) Thread.sleep(expirationTime / 2); - assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A)); + rollback = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TestApp.A); + assertThat(rollback).isNull(); + + } finally { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT, + RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS, + Long.toString(defaultExpirationTime), false /* makeDefault*/); + InstallUtils.dropShellPermissionIdentity(); + } + } + + /** + * Test that available rollbacks should expire correctly when the property + * {@link RollbackManager#PROPERTY_ROLLBACK_LIFETIME_MILLIS} is changed + */ + @Test + public void testRollbackExpiresWhenLifetimeChanges() throws Exception { + long defaultExpirationTime = TimeUnit.HOURS.toMillis(48); + RollbackManager rm = RollbackUtils.getRollbackManager(); + try { + InstallUtils.adoptShellPermissionIdentity( + Manifest.permission.INSTALL_PACKAGES, + Manifest.permission.DELETE_PACKAGES, + Manifest.permission.TEST_MANAGE_ROLLBACKS, + Manifest.permission.WRITE_DEVICE_CONFIG); + + Uninstall.packages(TestApp.A); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); + Install.single(TestApp.A1).commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + Install.single(TestApp.A2).setEnableRollback().commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + RollbackInfo rollback = waitForAvailableRollback(TestApp.A); + assertThat(rollback).packagesContainsExactly(Rollback.from(TestApp.A2).to(TestApp.A1)); + + // Change the lifetime to 0 which should expire rollbacks immediately + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT, + RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS, + Long.toString(0), false /* makeDefault*/); + + // Keep polling until device config changes has happened (which might take more than + // 5 sec depending how busy system_server is) and rollbacks have expired + for (int i = 0; i < 30; ++i) { + if (hasRollbackInclude(rm.getAvailableRollbacks(), TestApp.A)) { + Thread.sleep(1000); + } + } + rollback = getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A); + assertThat(rollback).isNull(); } finally { DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT, RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS, Long.toString(defaultExpirationTime), false /* makeDefault*/); - RollbackTestUtils.dropShellPermissionIdentity(); + InstallUtils.dropShellPermissionIdentity(); } } @@ -380,10 +506,10 @@ public class RollbackTest { public void testTimeChangeDoesNotAffectLifetime() throws Exception { long expirationTime = TimeUnit.SECONDS.toMillis(30); long defaultExpirationTime = TimeUnit.HOURS.toMillis(48); - RollbackManager rm = RollbackTestUtils.getRollbackManager(); + RollbackManager rm = RollbackUtils.getRollbackManager(); try { - RollbackTestUtils.adoptShellPermissionIdentity( + InstallUtils.adoptShellPermissionIdentity( Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES, Manifest.permission.TEST_MANAGE_ROLLBACKS, @@ -394,28 +520,26 @@ public class RollbackTest { RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS, Long.toString(expirationTime), false /* makeDefault*/); - // Pull the new expiration time from DeviceConfig - rm.reloadPersistedData(); - // Install app A with rollback enabled - RollbackTestUtils.uninstall(TEST_APP_A); - RollbackTestUtils.install("RollbackTestAppAv1.apk", false); - RollbackTestUtils.install("RollbackTestAppAv2.apk", true); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + Uninstall.packages(TestApp.A); + Install.single(TestApp.A1).commit(); + Install.single(TestApp.A2).setEnableRollback().commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); Thread.sleep(expirationTime / 2); // Install app B with rollback enabled - RollbackTestUtils.uninstall(TEST_APP_B); - RollbackTestUtils.install("RollbackTestAppBv1.apk", false); - RollbackTestUtils.install("RollbackTestAppBv2.apk", true); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); + Uninstall.packages(TestApp.B); + Install.single(TestApp.B1).commit(); + Install.single(TestApp.B2).setEnableRollback().commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); + // 1 second buffer Thread.sleep(1000); try { // Change the time - RollbackTestUtils.forwardTimeBy(expirationTime); + RollbackUtils.forwardTimeBy(expirationTime); // 1 second buffer to allow Rollback Manager to handle time change before loading // persisted data @@ -427,24 +551,32 @@ public class RollbackTest { // Wait until rollback for app A has expired // This will trigger an expiration run that should expire app A but not B Thread.sleep(expirationTime / 2); - assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A)); + RollbackInfo rollbackA = + getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A); + Log.i(TAG, "Checking if the rollback for TestApp.A is null"); // Rollback for app B should not be expired - RollbackInfo rollback = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_B); - assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollback); + RollbackInfo rollbackB1 = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TestApp.B); // Wait until rollback for app B has expired Thread.sleep(expirationTime / 2); - assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_B)); + RollbackInfo rollbackB2 = getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TestApp.B); + + assertThat(rollbackA).isNull(); + assertThat(rollbackB1).isNotNull(); + assertThat(rollbackB1).packagesContainsExactly( + Rollback.from(TestApp.B2).to(TestApp.B1)); + assertThat(rollbackB2).isNull(); } finally { - RollbackTestUtils.forwardTimeBy(-expirationTime); + RollbackUtils.forwardTimeBy(-expirationTime); } } finally { DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT, RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS, Long.toString(defaultExpirationTime), false /* makeDefault*/); - RollbackTestUtils.dropShellPermissionIdentity(); + InstallUtils.dropShellPermissionIdentity(); } } @@ -455,30 +587,30 @@ public class RollbackTest { @Test public void testRollbackExpiration() throws Exception { try { - RollbackTestUtils.adoptShellPermissionIdentity( + InstallUtils.adoptShellPermissionIdentity( Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES, Manifest.permission.TEST_MANAGE_ROLLBACKS); - RollbackManager rm = RollbackTestUtils.getRollbackManager(); - RollbackTestUtils.uninstall(TEST_APP_A); - RollbackTestUtils.install("RollbackTestAppAv1.apk", false); - RollbackTestUtils.install("RollbackTestAppAv2.apk", true); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + RollbackManager rm = RollbackUtils.getRollbackManager(); + Uninstall.packages(TestApp.A); + Install.single(TestApp.A1).commit(); + Install.single(TestApp.A2).setEnableRollback().commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); // The app should now be available for rollback. - RollbackInfo rollback = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_A); - assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback); + RollbackInfo rollback = waitForAvailableRollback(TestApp.A); + assertThat(rollback).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1)); // Expire the rollback. - rm.expireRollbackForPackage(TEST_APP_A); + rm.expireRollbackForPackage(TestApp.A); // The rollback should no longer be available. - assertNull(getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_A)); + assertThat(getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TestApp.A)).isNull(); } finally { - RollbackTestUtils.dropShellPermissionIdentity(); + InstallUtils.dropShellPermissionIdentity(); } } @@ -488,24 +620,22 @@ public class RollbackTest { @Test public void testUserDataRollback() throws Exception { try { - RollbackTestUtils.adoptShellPermissionIdentity( + InstallUtils.adoptShellPermissionIdentity( Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES, Manifest.permission.TEST_MANAGE_ROLLBACKS); - RollbackTestUtils.uninstall(TEST_APP_A); - RollbackTestUtils.install("RollbackTestAppAv1.apk", false); - processUserData(TEST_APP_A); - RollbackTestUtils.install("RollbackTestAppAv2.apk", true); - processUserData(TEST_APP_A); + Uninstall.packages(TestApp.A); + Install.single(TestApp.A1).commit(); + processUserData(TestApp.A); + Install.single(TestApp.A2).setEnableRollback().commit(); + processUserData(TestApp.A); - RollbackManager rm = RollbackTestUtils.getRollbackManager(); - RollbackInfo rollback = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_A); - RollbackTestUtils.rollback(rollback.getRollbackId()); - processUserData(TEST_APP_A); + RollbackInfo rollback = waitForAvailableRollback(TestApp.A); + RollbackUtils.rollback(rollback.getRollbackId()); + processUserData(TestApp.A); } finally { - RollbackTestUtils.dropShellPermissionIdentity(); + InstallUtils.dropShellPermissionIdentity(); } } @@ -515,30 +645,23 @@ public class RollbackTest { @Test public void testRollbackWithSplits() throws Exception { try { - RollbackTestUtils.adoptShellPermissionIdentity( + InstallUtils.adoptShellPermissionIdentity( Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES, Manifest.permission.TEST_MANAGE_ROLLBACKS); - RollbackTestUtils.uninstall(TEST_APP_A); - RollbackTestUtils.installSplit(false, - "RollbackTestAppASplitV1.apk", - "RollbackTestAppASplitV1_anydpi.apk"); - processUserData(TEST_APP_A); + Uninstall.packages(TestApp.A); + Install.single(TestApp.ASplit1).commit(); + processUserData(TestApp.A); - RollbackTestUtils.installSplit(true, - "RollbackTestAppASplitV2.apk", - "RollbackTestAppASplitV2_anydpi.apk"); - processUserData(TEST_APP_A); + Install.single(TestApp.ASplit2).setEnableRollback().commit(); + processUserData(TestApp.A); - RollbackManager rm = RollbackTestUtils.getRollbackManager(); - RollbackInfo rollback = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_A); - assertNotNull(rollback); - RollbackTestUtils.rollback(rollback.getRollbackId()); - processUserData(TEST_APP_A); + RollbackInfo rollback = waitForAvailableRollback(TestApp.A); + RollbackUtils.rollback(rollback.getRollbackId()); + processUserData(TestApp.A); } finally { - RollbackTestUtils.dropShellPermissionIdentity(); + InstallUtils.dropShellPermissionIdentity(); } } @@ -559,7 +682,7 @@ public class RollbackTest { // Confirm that we really haven't received the broadcast. // TODO: How long to wait for the expected timeout? - assertNull(broadcastReceiver.poll(5, TimeUnit.SECONDS)); + assertThat(broadcastReceiver.poll(5, TimeUnit.SECONDS)).isNull(); // TODO: Do we need to do this? Do we need to ensure this is always // called, even when the test fails? @@ -573,48 +696,47 @@ public class RollbackTest { @Test public void testMultipleRollbackAvailable() throws Exception { try { - RollbackTestUtils.adoptShellPermissionIdentity( + InstallUtils.adoptShellPermissionIdentity( Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES, Manifest.permission.TEST_MANAGE_ROLLBACKS); - RollbackManager rm = RollbackTestUtils.getRollbackManager(); // Prep installation of the test apps. - RollbackTestUtils.uninstall(TEST_APP_A); - RollbackTestUtils.install("RollbackTestAppAv1.apk", false); - RollbackTestUtils.install("RollbackTestAppAv2.apk", true); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + Uninstall.packages(TestApp.A); + Install.single(TestApp.A1).commit(); + Install.single(TestApp.A2).setEnableRollback().commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - RollbackTestUtils.uninstall(TEST_APP_B); - RollbackTestUtils.install("RollbackTestAppBv1.apk", false); - RollbackTestUtils.install("RollbackTestAppBv2.apk", true); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); + Uninstall.packages(TestApp.B); + Install.single(TestApp.B1).commit(); + Install.single(TestApp.B2).setEnableRollback().commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); // Both test apps should now be available for rollback, and the // RollbackInfo returned for the rollbacks should be correct. - RollbackInfo rollbackA = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_A); - assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA); + RollbackInfo rollbackA = waitForAvailableRollback(TestApp.A); + assertThat(rollbackA).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1)); - RollbackInfo rollbackB = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_B); - assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB); + RollbackInfo rollbackB = waitForAvailableRollback(TestApp.B); + assertThat(rollbackB).packagesContainsExactly( + Rollback.from(TestApp.B2).to(TestApp.B1)); // Executing rollback should roll back the correct package. - RollbackTestUtils.rollback(rollbackA.getRollbackId()); - assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); - - RollbackTestUtils.uninstall(TEST_APP_A); - RollbackTestUtils.install("RollbackTestAppAv1.apk", false); - RollbackTestUtils.install("RollbackTestAppAv2.apk", true); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); - - RollbackTestUtils.rollback(rollbackB.getRollbackId()); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); - assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); + RollbackUtils.rollback(rollbackA.getRollbackId()); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); + + Uninstall.packages(TestApp.A); + Install.single(TestApp.A1).commit(); + Install.single(TestApp.A2).setEnableRollback().commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + + RollbackUtils.rollback(rollbackB.getRollbackId()); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); } finally { - RollbackTestUtils.dropShellPermissionIdentity(); + InstallUtils.dropShellPermissionIdentity(); } } @@ -626,7 +748,7 @@ public class RollbackTest { public void testManageRollbacksPermission() throws Exception { // We shouldn't be allowed to call any of the RollbackManager APIs // without the MANAGE_ROLLBACKS permission. - RollbackManager rm = RollbackTestUtils.getRollbackManager(); + RollbackManager rm = RollbackUtils.getRollbackManager(); try { rm.getAvailableRollbacks(); @@ -659,7 +781,7 @@ public class RollbackTest { } try { - rm.expireRollbackForPackage(TEST_APP_A); + rm.expireRollbackForPackage(TestApp.A); fail("expected SecurityException"); } catch (SecurityException e) { // Expected. @@ -673,26 +795,27 @@ public class RollbackTest { @Test public void testEnableRollbackPermission() throws Exception { try { - RollbackTestUtils.adoptShellPermissionIdentity( + InstallUtils.adoptShellPermissionIdentity( Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES); - RollbackTestUtils.uninstall(TEST_APP_A); - RollbackTestUtils.install("RollbackTestAppAv1.apk", /* enableRollback */ false); - assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + Uninstall.packages(TestApp.A); + Install.single(TestApp.A1).commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - RollbackTestUtils.install("RollbackTestAppAv2.apk", /* enableRollback */ true); + Install.single(TestApp.A2).setEnableRollback().commit(); // We expect v2 of the app was installed, but rollback has not // been enabled. - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - RollbackTestUtils.adoptShellPermissionIdentity( + InstallUtils.adoptShellPermissionIdentity( Manifest.permission.TEST_MANAGE_ROLLBACKS); - RollbackManager rm = RollbackTestUtils.getRollbackManager(); - assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A)); + RollbackManager rm = RollbackUtils.getRollbackManager(); + assertThat( + getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A)).isNull(); } finally { - RollbackTestUtils.dropShellPermissionIdentity(); + InstallUtils.dropShellPermissionIdentity(); } } @@ -703,25 +826,26 @@ public class RollbackTest { @Test public void testNonModuleEnableRollback() throws Exception { try { - RollbackTestUtils.adoptShellPermissionIdentity( + InstallUtils.adoptShellPermissionIdentity( Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES, Manifest.permission.MANAGE_ROLLBACKS); - RollbackTestUtils.uninstall(TEST_APP_A); - RollbackTestUtils.install("RollbackTestAppAv1.apk", /* enableRollback */ false); - assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + Uninstall.packages(TestApp.A); + Install.single(TestApp.A1).commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - RollbackTestUtils.install("RollbackTestAppAv2.apk", /* enableRollback */ true); + Install.single(TestApp.A2).setEnableRollback().commit(); // We expect v2 of the app was installed, but rollback has not // been enabled because the test app is not a module. - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - RollbackManager rm = RollbackTestUtils.getRollbackManager(); - assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A)); + RollbackManager rm = RollbackUtils.getRollbackManager(); + assertThat( + getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A)).isNull(); } finally { - RollbackTestUtils.dropShellPermissionIdentity(); + InstallUtils.dropShellPermissionIdentity(); } } @@ -731,54 +855,53 @@ public class RollbackTest { @Test public void testMultiPackage() throws Exception { try { - RollbackTestUtils.adoptShellPermissionIdentity( + InstallUtils.adoptShellPermissionIdentity( Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES, Manifest.permission.TEST_MANAGE_ROLLBACKS); - RollbackManager rm = RollbackTestUtils.getRollbackManager(); + RollbackManager rm = RollbackUtils.getRollbackManager(); // Prep installation of the test apps. - RollbackTestUtils.uninstall(TEST_APP_A); - RollbackTestUtils.uninstall(TEST_APP_B); - RollbackTestUtils.installMultiPackage(false, - "RollbackTestAppAv1.apk", - "RollbackTestAppBv1.apk"); - processUserData(TEST_APP_A); - processUserData(TEST_APP_B); - RollbackTestUtils.installMultiPackage(true, - "RollbackTestAppAv2.apk", - "RollbackTestAppBv2.apk"); - processUserData(TEST_APP_A); - processUserData(TEST_APP_B); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); - - // TEST_APP_A should now be available for rollback. - RollbackInfo rollback = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_A); - assertRollbackInfoForAandB(rollback); + Uninstall.packages(TestApp.A, TestApp.B); + Install.multi(TestApp.A1, TestApp.B1).commit(); + processUserData(TestApp.A); + processUserData(TestApp.B); + Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit(); + processUserData(TestApp.A); + processUserData(TestApp.B); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); + + // TestApp.A should now be available for rollback. + RollbackInfo rollback = waitForAvailableRollback(TestApp.A); + assertThat(rollback).isNotNull(); + assertThat(rollback).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1), + Rollback.from(TestApp.B2).to(TestApp.B1)); // Rollback the app. It should cause both test apps to be rolled // back. - RollbackTestUtils.rollback(rollback.getRollbackId()); - assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); - assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); + RollbackUtils.rollback(rollback.getRollbackId()); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); // We should see recent rollbacks listed for both A and B. Thread.sleep(1000); RollbackInfo rollbackA = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TEST_APP_A); + rm.getRecentlyCommittedRollbacks(), TestApp.A); RollbackInfo rollbackB = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TEST_APP_B); - assertRollbackInfoForAandB(rollbackB); + rm.getRecentlyCommittedRollbacks(), TestApp.B); + assertThat(rollback).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1), + Rollback.from(TestApp.B2).to(TestApp.B1)); - assertEquals(rollbackA.getRollbackId(), rollbackB.getRollbackId()); + assertThat(rollbackA).hasRollbackId(rollbackB.getRollbackId()); - processUserData(TEST_APP_A); - processUserData(TEST_APP_B); + processUserData(TestApp.A); + processUserData(TestApp.B); } finally { - RollbackTestUtils.dropShellPermissionIdentity(); + InstallUtils.dropShellPermissionIdentity(); } } @@ -790,31 +913,27 @@ public class RollbackTest { @Test public void testMultiPackageEnableFail() throws Exception { try { - RollbackTestUtils.adoptShellPermissionIdentity( + InstallUtils.adoptShellPermissionIdentity( Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES, Manifest.permission.TEST_MANAGE_ROLLBACKS); - RollbackManager rm = RollbackTestUtils.getRollbackManager(); - - RollbackTestUtils.uninstall(TEST_APP_A); - RollbackTestUtils.uninstall(TEST_APP_B); - RollbackTestUtils.install("RollbackTestAppAv1.apk", false); + RollbackManager rm = RollbackUtils.getRollbackManager(); + Uninstall.packages(TestApp.A, TestApp.B); + Install.single(TestApp.A1).commit(); // We should fail to enable rollback here because TestApp B is not // already installed. - RollbackTestUtils.installMultiPackage(true, - "RollbackTestAppAv2.apk", - "RollbackTestAppBv2.apk"); + Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit(); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); - assertNull(getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_A)); - assertNull(getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_B)); + assertThat(getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TestApp.A)).isNull(); + assertThat(getUniqueRollbackInfoForPackage( + rm.getAvailableRollbacks(), TestApp.B)).isNull(); } finally { - RollbackTestUtils.dropShellPermissionIdentity(); + InstallUtils.dropShellPermissionIdentity(); } } @@ -825,30 +944,33 @@ public class RollbackTest { */ public void testSameVersionUpdate() throws Exception { try { - RollbackTestUtils.adoptShellPermissionIdentity( + InstallUtils.adoptShellPermissionIdentity( Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES, Manifest.permission.TEST_MANAGE_ROLLBACKS); - RollbackManager rm = RollbackTestUtils.getRollbackManager(); + RollbackManager rm = RollbackUtils.getRollbackManager(); - RollbackTestUtils.uninstall(TEST_APP_A); - RollbackTestUtils.install("RollbackTestAppAv1.apk", false); - RollbackTestUtils.install("RollbackTestAppAv2.apk", true); - RollbackTestUtils.install("RollbackTestAppACrashingV2.apk", true); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + Uninstall.packages(TestApp.A); + Install.single(TestApp.A1).commit(); + Install.single(TestApp.A2).setEnableRollback().commit(); + Install.single(TestApp.ACrashing2).setEnableRollback().commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); RollbackInfo rollback = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_A); - assertRollbackInfoEquals(TEST_APP_A, 2, 2, rollback); + rm.getAvailableRollbacks(), TestApp.A); + assertThat(rollback).isNotNull(); + assertThat(rollback).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A2)); - RollbackTestUtils.rollback(rollback.getRollbackId()); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + RollbackUtils.rollback(rollback.getRollbackId()); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); rollback = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TEST_APP_A); - assertRollbackInfoEquals(TEST_APP_A, 2, 2, rollback); + rm.getRecentlyCommittedRollbacks(), TestApp.A); + assertThat(rollback).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A2)); } finally { - RollbackTestUtils.dropShellPermissionIdentity(); + InstallUtils.dropShellPermissionIdentity(); } } @@ -857,60 +979,54 @@ public class RollbackTest { */ @Test public void testBadUpdateRollback() throws Exception { - BroadcastReceiver crashCountReceiver = null; Context context = InstrumentationRegistry.getContext(); try { - RollbackTestUtils.adoptShellPermissionIdentity( + InstallUtils.adoptShellPermissionIdentity( Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES, Manifest.permission.MANAGE_ROLLBACKS, Manifest.permission.TEST_MANAGE_ROLLBACKS, - Manifest.permission.KILL_BACKGROUND_PROCESSES, + Manifest.permission.FORCE_STOP_PACKAGES, Manifest.permission.RESTART_PACKAGES); - RollbackManager rm = RollbackTestUtils.getRollbackManager(); // Prep installation of the test apps. - RollbackTestUtils.uninstall(TEST_APP_A); - RollbackTestUtils.install("RollbackTestAppAv1.apk", false); - RollbackTestUtils.install("RollbackTestAppACrashingV2.apk", true); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + Uninstall.packages(TestApp.A, TestApp.B); + Install.single(TestApp.A1).commit(); + Install.single(TestApp.ACrashing2).setEnableRollback().commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - RollbackTestUtils.uninstall(TEST_APP_B); - RollbackTestUtils.install("RollbackTestAppBv1.apk", false); - RollbackTestUtils.install("RollbackTestAppBv2.apk", true); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); + Install.single(TestApp.B1).commit(); + Install.single(TestApp.B2).setEnableRollback().commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); // Both test apps should now be available for rollback, and the // targetPackage returned for rollback should be correct. - RollbackInfo rollbackA = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_A); - assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA); + RollbackInfo rollbackA = waitForAvailableRollback(TestApp.A); + assertThat(rollbackA).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1)); - RollbackInfo rollbackB = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_B); - assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB); + RollbackInfo rollbackB = waitForAvailableRollback(TestApp.B); + assertThat(rollbackB).packagesContainsExactly( + Rollback.from(TestApp.B2).to(TestApp.B1)); // Register rollback committed receiver RollbackBroadcastReceiver rollbackReceiver = new RollbackBroadcastReceiver(); - // Crash TEST_APP_A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback - crashCountReceiver = RollbackTestUtils.sendCrashBroadcast(context, TEST_APP_A, 5); + // Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback + RollbackUtils.sendCrashBroadcast(TestApp.A, 5); // Verify we received a broadcast for the rollback. rollbackReceiver.take(); - // TEST_APP_A is automatically rolled back by the RollbackPackageHealthObserver - assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + // TestApp.A is automatically rolled back by the RollbackPackageHealthObserver + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); // Instrumented app is still the package installer - String installer = context.getPackageManager().getInstallerPackageName(TEST_APP_A); - assertEquals(INSTRUMENTED_APP, installer); - // TEST_APP_B is untouched - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B)); + String installer = context.getPackageManager().getInstallerPackageName(TestApp.A); + assertThat(installer).isEqualTo(INSTRUMENTED_APP); + // TestApp.B is untouched + assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); } finally { - RollbackTestUtils.dropShellPermissionIdentity(); - if (crashCountReceiver != null) { - context.unregisterReceiver(crashCountReceiver); - } + InstallUtils.dropShellPermissionIdentity(); } } @@ -920,31 +1036,31 @@ public class RollbackTest { @Test public void testRollForwardRace() throws Exception { try { - RollbackTestUtils.adoptShellPermissionIdentity( + InstallUtils.adoptShellPermissionIdentity( Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES, Manifest.permission.TEST_MANAGE_ROLLBACKS, Manifest.permission.MANAGE_ROLLBACKS); - RollbackManager rm = RollbackTestUtils.getRollbackManager(); + RollbackManager rm = RollbackUtils.getRollbackManager(); - RollbackTestUtils.uninstall(TEST_APP_A); - RollbackTestUtils.install("RollbackTestAppAv1.apk", false); - RollbackTestUtils.install("RollbackTestAppAv2.apk", true); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + Uninstall.packages(TestApp.A); + Install.single(TestApp.A1).commit(); + Install.single(TestApp.A2).setEnableRollback().commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - RollbackInfo rollback = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_A); - assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback); + RollbackInfo rollback = waitForAvailableRollback(TestApp.A); + assertThat(rollback).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1)); // Install a new version of package A, then immediately rollback // the previous version. We expect the rollback to fail, because // it is no longer available. // There are a couple different ways this could fail depending on // thread interleaving, so don't ignore flaky failures. - RollbackTestUtils.install("RollbackTestAppAv3.apk", false); + Install.single(TestApp.A3).commit(); try { - RollbackTestUtils.rollback(rollback.getRollbackId()); + RollbackUtils.rollback(rollback.getRollbackId()); // Note: Don't ignore flaky failures here. fail("Expected rollback to fail, but it did not."); } catch (AssertionError e) { @@ -953,17 +1069,16 @@ public class RollbackTest { } // Note: Don't ignore flaky failures here. - assertEquals(3, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(3); } finally { - RollbackTestUtils.dropShellPermissionIdentity(); + InstallUtils.dropShellPermissionIdentity(); } } @Test - @Ignore("b/136605788") public void testEnableRollbackTimeoutFailsRollback() throws Exception { try { - RollbackTestUtils.adoptShellPermissionIdentity( + InstallUtils.adoptShellPermissionIdentity( Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES, Manifest.permission.TEST_MANAGE_ROLLBACKS, @@ -973,36 +1088,135 @@ public class RollbackTest { //setting the timeout to a very short amount that will definitely be triggered DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS, - Long.toString(1), false /* makeDefault*/); - RollbackManager rm = RollbackTestUtils.getRollbackManager(); + Long.toString(0), false /* makeDefault*/); + RollbackManager rm = RollbackUtils.getRollbackManager(); + + Uninstall.packages(TestApp.A); + Install.single(TestApp.A1).commit(); + waitForUnavailableRollback(TestApp.A); + + // Block the RollbackManager to make extra sure it will not be + // able to enable the rollback in time. + rm.blockRollbackManager(TimeUnit.SECONDS.toMillis(1)); + Install.single(TestApp.A2).setEnableRollback().commit(); + + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + + // Give plenty of time for RollbackManager to unblock and attempt + // to make the rollback available before asserting that the + // rollback was not made available. + Thread.sleep(TimeUnit.SECONDS.toMillis(2)); + assertThat( + getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A)).isNull(); + } finally { + //setting the timeout back to default + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, + PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS, + null, false /* makeDefault*/); + InstallUtils.dropShellPermissionIdentity(); + } + } + + @Test + public void testEnableRollbackTimeoutFailsRollback_MultiPackage() throws Exception { + try { + InstallUtils.adoptShellPermissionIdentity( + Manifest.permission.INSTALL_PACKAGES, + Manifest.permission.DELETE_PACKAGES, + Manifest.permission.TEST_MANAGE_ROLLBACKS, + Manifest.permission.MANAGE_ROLLBACKS, + Manifest.permission.WRITE_DEVICE_CONFIG); + + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, + PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS, + Long.toString(5000), false /* makeDefault*/); + RollbackManager rm = RollbackUtils.getRollbackManager(); + + Uninstall.packages(TestApp.A, TestApp.B); + Install.multi(TestApp.A1, TestApp.B1).commit(); + waitForUnavailableRollback(TestApp.A); + + // Block the 2nd session for 10s so it will not be able to enable the rollback in time. + rm.blockRollbackManager(TimeUnit.SECONDS.toMillis(0)); + rm.blockRollbackManager(TimeUnit.SECONDS.toMillis(10)); + Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit(); - RollbackTestUtils.uninstall(TEST_APP_A); - RollbackTestUtils.install("RollbackTestAppAv1.apk", false); - RollbackTestUtils.install("RollbackTestAppAv2.apk", true); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); + // Give plenty of time for RollbackManager to unblock and attempt + // to make the rollback available before asserting that the + // rollback was not made available. + Thread.sleep(TimeUnit.SECONDS.toMillis(2)); - assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A)); + List<RollbackInfo> available = rm.getAvailableRollbacks(); + assertThat(getUniqueRollbackInfoForPackage(available, TestApp.A)).isNull(); + assertThat(getUniqueRollbackInfoForPackage(available, TestApp.B)).isNull(); } finally { //setting the timeout back to default DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS, null, false /* makeDefault*/); - RollbackTestUtils.dropShellPermissionIdentity(); + InstallUtils.dropShellPermissionIdentity(); } } - // Helper function to test that the given rollback info is a rollback for - // the atomic set {A2, B2} -> {A1, B1}. - private void assertRollbackInfoForAandB(RollbackInfo rollback) { - assertNotNull(rollback); - assertEquals(2, rollback.getPackages().size()); - if (TEST_APP_A.equals(rollback.getPackages().get(0).getPackageName())) { - assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.getPackages().get(0)); - assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollback.getPackages().get(1)); - } else { - assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollback.getPackages().get(0)); - assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.getPackages().get(1)); + /** + * Test we can't enable rollback for non-whitelisted app without + * TEST_MANAGE_ROLLBACKS permission + */ + @Test + public void testNonRollbackWhitelistedApp() throws Exception { + try { + InstallUtils.adoptShellPermissionIdentity( + Manifest.permission.INSTALL_PACKAGES, + Manifest.permission.DELETE_PACKAGES, + Manifest.permission.MANAGE_ROLLBACKS); + + Uninstall.packages(TestApp.A); + Install.single(TestApp.A1).commit(); + assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull(); + + Install.single(TestApp.A2).setEnableRollback().commit(); + Thread.sleep(TimeUnit.SECONDS.toMillis(2)); + assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull(); + } finally { + InstallUtils.dropShellPermissionIdentity(); + } + } + + @Test + public void testRollbackDataPolicy() throws Exception { + try { + InstallUtils.adoptShellPermissionIdentity( + Manifest.permission.INSTALL_PACKAGES, + Manifest.permission.DELETE_PACKAGES, + Manifest.permission.TEST_MANAGE_ROLLBACKS); + + Uninstall.packages(TestApp.A, TestApp.B); + Install.multi(TestApp.A1, TestApp.B1).commit(); + // Write user data version = 1 + InstallUtils.processUserData(TestApp.A); + InstallUtils.processUserData(TestApp.B); + + Install a2 = Install.single(TestApp.A2) + .setEnableRollback(PackageManager.RollbackDataPolicy.WIPE); + Install b2 = Install.single(TestApp.B2) + .setEnableRollback(PackageManager.RollbackDataPolicy.RESTORE); + Install.multi(a2, b2).setEnableRollback().commit(); + // Write user data version = 2 + InstallUtils.processUserData(TestApp.A); + InstallUtils.processUserData(TestApp.B); + + RollbackInfo info = RollbackUtils.getAvailableRollback(TestApp.A); + RollbackUtils.rollback(info.getRollbackId()); + // Read user data version from userdata.txt + // A's user data version is -1 for user data is wiped. + // B's user data version is 1 as rollback committed. + assertThat(InstallUtils.getUserDataVersion(TestApp.A)).isEqualTo(-1); + assertThat(InstallUtils.getUserDataVersion(TestApp.B)).isEqualTo(1); + } finally { + InstallUtils.dropShellPermissionIdentity(); } } } diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java deleted file mode 100644 index a9e20cdb191b..000000000000 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java +++ /dev/null @@ -1,547 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tests.rollback; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.fail; - -import android.app.ActivityManager; -import android.app.AlarmManager; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageInstaller; -import android.content.pm.PackageManager; -import android.content.pm.VersionedPackage; -import android.content.rollback.PackageRollbackInfo; -import android.content.rollback.RollbackInfo; -import android.content.rollback.RollbackManager; -import android.os.Handler; -import android.os.HandlerThread; -import android.util.Log; - -import androidx.test.InstrumentationRegistry; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.SynchronousQueue; - -/** - * Utilities to facilitate testing rollbacks. - */ -class RollbackTestUtils { - - private static final String TAG = "RollbackTest"; - - static RollbackManager getRollbackManager() { - Context context = InstrumentationRegistry.getContext(); - RollbackManager rm = (RollbackManager) context.getSystemService(Context.ROLLBACK_SERVICE); - if (rm == null) { - throw new AssertionError("Failed to get RollbackManager"); - } - return rm; - } - - private static void setTime(long millis) { - Context context = InstrumentationRegistry.getContext(); - AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - am.setTime(millis); - } - - static void forwardTimeBy(long offsetMillis) { - setTime(System.currentTimeMillis() + offsetMillis); - Log.i(TAG, "Forwarded time on device by " + offsetMillis + " millis"); - } - - /** - * Returns the version of the given package installed on device. - * Returns -1 if the package is not currently installed. - */ - static long getInstalledVersion(String packageName) { - PackageInfo pi = getPackageInfo(packageName); - if (pi == null) { - return -1; - } else { - return pi.getLongVersionCode(); - } - } - - private static boolean isSystemAppWithoutUpdate(String packageName) { - PackageInfo pi = getPackageInfo(packageName); - if (pi == null) { - return false; - } else { - return ((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) - && ((pi.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0); - } - } - - private static PackageInfo getPackageInfo(String packageName) { - Context context = InstrumentationRegistry.getContext(); - PackageManager pm = context.getPackageManager(); - try { - return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX); - } catch (PackageManager.NameNotFoundException e) { - return null; - } - } - - private static void assertStatusSuccess(Intent result) { - int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, - PackageInstaller.STATUS_FAILURE); - if (status == -1) { - throw new AssertionError("PENDING USER ACTION"); - } else if (status > 0) { - String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE); - throw new AssertionError(message == null ? "UNKNOWN FAILURE" : message); - } - } - - /** - * Uninstalls the given package. - * Does nothing if the package is not installed. - * @throws AssertionError if package can't be uninstalled. - */ - static void uninstall(String packageName) throws InterruptedException, IOException { - // No need to uninstall if the package isn't installed or is installed on /system. - if (getInstalledVersion(packageName) == -1 || isSystemAppWithoutUpdate(packageName)) { - return; - } - - Context context = InstrumentationRegistry.getContext(); - PackageManager packageManager = context.getPackageManager(); - PackageInstaller packageInstaller = packageManager.getPackageInstaller(); - packageInstaller.uninstall(packageName, LocalIntentSender.getIntentSender()); - assertStatusSuccess(LocalIntentSender.getIntentSenderResult()); - } - - /** - * Commit the given rollback. - * @throws AssertionError if the rollback fails. - */ - static void rollback(int rollbackId, VersionedPackage... causePackages) - throws InterruptedException { - RollbackManager rm = getRollbackManager(); - rm.commitRollback(rollbackId, Arrays.asList(causePackages), - LocalIntentSender.getIntentSender()); - Intent result = LocalIntentSender.getIntentSenderResult(); - int status = result.getIntExtra(RollbackManager.EXTRA_STATUS, - RollbackManager.STATUS_FAILURE); - if (status != RollbackManager.STATUS_SUCCESS) { - String message = result.getStringExtra(RollbackManager.EXTRA_STATUS_MESSAGE); - throw new AssertionError(message); - } - } - - /** - * Installs the apk with the given name. - * - * @param resourceName name of class loader resource for the apk to - * install. - * @param enableRollback if rollback should be enabled. - * @throws AssertionError if the installation fails. - */ - static void install(String resourceName, boolean enableRollback) - throws InterruptedException, IOException { - installSplit(enableRollback, resourceName); - } - - /** - * Installs the apk with the given name and its splits. - * - * @param enableRollback if rollback should be enabled. - * @param resourceNames names of class loader resources for the apk and - * its splits to install. - * @throws AssertionError if the installation fails. - */ - static void installSplit(boolean enableRollback, String... resourceNames) - throws InterruptedException, IOException { - Context context = InstrumentationRegistry.getContext(); - PackageInstaller.Session session = null; - PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller(); - PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( - PackageInstaller.SessionParams.MODE_FULL_INSTALL); - params.setEnableRollback(enableRollback); - int sessionId = packageInstaller.createSession(params); - session = packageInstaller.openSession(sessionId); - - ClassLoader loader = RollbackTest.class.getClassLoader(); - for (String resourceName : resourceNames) { - try (OutputStream packageInSession = session.openWrite(resourceName, 0, -1); - InputStream is = loader.getResourceAsStream(resourceName);) { - byte[] buffer = new byte[4096]; - int n; - while ((n = is.read(buffer)) >= 0) { - packageInSession.write(buffer, 0, n); - } - } - } - - // Commit the session (this will start the installation workflow). - session.commit(LocalIntentSender.getIntentSender()); - assertStatusSuccess(LocalIntentSender.getIntentSenderResult()); - } - - /** Launches {@code packageName} with {@link Intent#ACTION_MAIN}. */ - private static void launchPackage(String packageName) - throws InterruptedException, IOException { - Context context = InstrumentationRegistry.getContext(); - Intent intent = new Intent(Intent.ACTION_MAIN); - intent.setPackage(packageName); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addCategory(Intent.CATEGORY_LAUNCHER); - context.startActivity(intent); - } - - /** - * Installs the APKs or APEXs with the given resource names as an atomic - * set. A resource is assumed to be an APEX if it has the .apex extension. - * <p> - * In case of staged installs, this function will return succesfully after - * the staged install has been committed and is ready for the device to - * reboot. - * - * @param staged if the rollback should be staged. - * @param enableRollback if rollback should be enabled. - * @param resourceNames names of the class loader resource for the apks to - * install. - * @throws AssertionError if the installation fails. - */ - private static void install(boolean staged, boolean enableRollback, - String... resourceNames) throws InterruptedException, IOException { - Context context = InstrumentationRegistry.getContext(); - PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller(); - - PackageInstaller.SessionParams multiPackageParams = new PackageInstaller.SessionParams( - PackageInstaller.SessionParams.MODE_FULL_INSTALL); - multiPackageParams.setMultiPackage(); - if (staged) { - multiPackageParams.setStaged(); - } - // TODO: Do we set this on the parent params, the child params, or - // both? - multiPackageParams.setEnableRollback(enableRollback); - int multiPackageId = packageInstaller.createSession(multiPackageParams); - PackageInstaller.Session multiPackage = packageInstaller.openSession(multiPackageId); - - for (String resourceName : resourceNames) { - PackageInstaller.Session session = null; - PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( - PackageInstaller.SessionParams.MODE_FULL_INSTALL); - if (staged) { - params.setStaged(); - } - if (resourceName.endsWith(".apex")) { - params.setInstallAsApex(); - } - params.setEnableRollback(enableRollback); - int sessionId = packageInstaller.createSession(params); - session = packageInstaller.openSession(sessionId); - - ClassLoader loader = RollbackTest.class.getClassLoader(); - try (OutputStream packageInSession = session.openWrite(resourceName, 0, -1); - InputStream is = loader.getResourceAsStream(resourceName);) { - byte[] buffer = new byte[4096]; - int n; - while ((n = is.read(buffer)) >= 0) { - packageInSession.write(buffer, 0, n); - } - } - multiPackage.addChildSessionId(sessionId); - } - - // Commit the session (this will start the installation workflow). - multiPackage.commit(LocalIntentSender.getIntentSender()); - assertStatusSuccess(LocalIntentSender.getIntentSenderResult()); - - if (staged) { - waitForSessionReady(multiPackageId); - } - } - - /** - * Installs the apks with the given resource names as an atomic set. - * - * @param enableRollback if rollback should be enabled. - * @param resourceNames names of the class loader resource for the apks to - * install. - * @throws AssertionError if the installation fails. - */ - static void installMultiPackage(boolean enableRollback, String... resourceNames) - throws InterruptedException, IOException { - install(false, enableRollback, resourceNames); - } - - /** - * Installs the APKs or APEXs with the given resource names as a staged - * atomic set. A resource is assumed to be an APEX if it has the .apex - * extension. - * - * @param enableRollback if rollback should be enabled. - * @param resourceNames names of the class loader resource for the apks to - * install. - * @throws AssertionError if the installation fails. - */ - static void installStaged(boolean enableRollback, String... resourceNames) - throws InterruptedException, IOException { - install(true, enableRollback, resourceNames); - } - - static void adoptShellPermissionIdentity(String... permissions) { - InstrumentationRegistry - .getInstrumentation() - .getUiAutomation() - .adoptShellPermissionIdentity(permissions); - } - - static void dropShellPermissionIdentity() { - InstrumentationRegistry - .getInstrumentation() - .getUiAutomation() - .dropShellPermissionIdentity(); - } - - /** - * Returns the RollbackInfo with a given package in the list of rollbacks. - * Throws an assertion failure if there is more than one such rollback - * info. Returns null if there are no such rollback infos. - */ - static RollbackInfo getUniqueRollbackInfoForPackage(List<RollbackInfo> rollbacks, - String packageName) { - RollbackInfo found = null; - for (RollbackInfo rollback : rollbacks) { - for (PackageRollbackInfo info : rollback.getPackages()) { - if (packageName.equals(info.getPackageName())) { - assertNull(found); - found = rollback; - break; - } - } - } - return found; - } - - /** - * Asserts that the given PackageRollbackInfo has the expected package - * name and versions. - */ - static void assertPackageRollbackInfoEquals(String packageName, - long versionRolledBackFrom, long versionRolledBackTo, - PackageRollbackInfo info) { - assertEquals(packageName, info.getPackageName()); - assertEquals(packageName, info.getVersionRolledBackFrom().getPackageName()); - assertEquals(versionRolledBackFrom, info.getVersionRolledBackFrom().getLongVersionCode()); - assertEquals(packageName, info.getVersionRolledBackTo().getPackageName()); - assertEquals(versionRolledBackTo, info.getVersionRolledBackTo().getLongVersionCode()); - } - - /** - * Asserts that the given RollbackInfo has the given packages with expected - * package names and all are rolled to and from the same given versions. - */ - static void assertRollbackInfoEquals(String[] packageNames, - long versionRolledBackFrom, long versionRolledBackTo, - RollbackInfo info, VersionedPackage... causePackages) { - assertNotNull(info); - assertEquals(packageNames.length, info.getPackages().size()); - int foundPackages = 0; - for (String packageName : packageNames) { - for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) { - if (packageName.equals(pkgRollbackInfo.getPackageName())) { - foundPackages++; - assertPackageRollbackInfoEquals(packageName, versionRolledBackFrom, - versionRolledBackTo, pkgRollbackInfo); - break; - } - } - } - assertEquals(packageNames.length, foundPackages); - assertEquals(causePackages.length, info.getCausePackages().size()); - for (int i = 0; i < causePackages.length; ++i) { - assertEquals(causePackages[i].getPackageName(), - info.getCausePackages().get(i).getPackageName()); - assertEquals(causePackages[i].getLongVersionCode(), - info.getCausePackages().get(i).getLongVersionCode()); - } - } - - /** - * Asserts that the given RollbackInfo has a single package with expected - * package name and versions. - */ - static void assertRollbackInfoEquals(String packageName, - long versionRolledBackFrom, long versionRolledBackTo, - RollbackInfo info, VersionedPackage... causePackages) { - String[] packageNames = {packageName}; - assertRollbackInfoEquals(packageNames, versionRolledBackFrom, versionRolledBackTo, info, - causePackages); - } - - /** - * Waits for the given session to be marked as ready. - * Throws an assertion if the session fails. - */ - static void waitForSessionReady(int sessionId) { - BlockingQueue<PackageInstaller.SessionInfo> sessionStatus = new LinkedBlockingQueue<>(); - BroadcastReceiver sessionUpdatedReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - PackageInstaller.SessionInfo info = - intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION); - if (info != null && info.getSessionId() == sessionId) { - if (info.isStagedSessionReady() || info.isStagedSessionFailed()) { - try { - sessionStatus.put(info); - } catch (InterruptedException e) { - Log.e(TAG, "Failed to put session info.", e); - } - } - } - } - }; - IntentFilter sessionUpdatedFilter = - new IntentFilter(PackageInstaller.ACTION_SESSION_UPDATED); - - Context context = InstrumentationRegistry.getContext(); - context.registerReceiver(sessionUpdatedReceiver, sessionUpdatedFilter); - - PackageInstaller installer = context.getPackageManager().getPackageInstaller(); - PackageInstaller.SessionInfo info = installer.getSessionInfo(sessionId); - - try { - if (info.isStagedSessionReady() || info.isStagedSessionFailed()) { - sessionStatus.put(info); - } - - info = sessionStatus.take(); - context.unregisterReceiver(sessionUpdatedReceiver); - if (info.isStagedSessionFailed()) { - throw new AssertionError(info.getStagedSessionErrorMessage()); - } - } catch (InterruptedException e) { - throw new AssertionError(e); - } - } - - private static final String NO_RESPONSE = "NO RESPONSE"; - - /** - * Calls into the test app to process user data. - * Asserts if the user data could not be processed or was version - * incompatible with the previously processed user data. - */ - static void processUserData(String packageName) { - Intent intent = new Intent(); - intent.setComponent(new ComponentName(packageName, - "com.android.tests.rollback.testapp.ProcessUserData")); - Context context = InstrumentationRegistry.getContext(); - - HandlerThread handlerThread = new HandlerThread("RollbackTestHandlerThread"); - handlerThread.start(); - - // It can sometimes take a while after rollback before the app will - // receive this broadcast, so try a few times in a loop. - String result = NO_RESPONSE; - for (int i = 0; result.equals(NO_RESPONSE) && i < 5; ++i) { - BlockingQueue<String> resultQueue = new LinkedBlockingQueue<>(); - context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (getResultCode() == 1) { - resultQueue.add("OK"); - } else { - // If the test app doesn't receive the broadcast or - // fails to set the result data, then getResultData - // here returns the initial NO_RESPONSE data passed to - // the sendOrderedBroadcast call. - resultQueue.add(getResultData()); - } - } - }, new Handler(handlerThread.getLooper()), 0, NO_RESPONSE, null); - - try { - result = resultQueue.take(); - } catch (InterruptedException e) { - throw new AssertionError(e); - } - } - - handlerThread.quit(); - if (!"OK".equals(result)) { - fail(result); - } - } - - /** - * Return the rollback info for a recently committed rollback, by matching the rollback id, or - * return null if no matching rollback is found. - */ - static RollbackInfo getRecentlyCommittedRollbackInfoById(int getRollbackId) { - for (RollbackInfo info : getRollbackManager().getRecentlyCommittedRollbacks()) { - if (info.getRollbackId() == getRollbackId) { - return info; - } - } - return null; - } - - /** - * Send broadcast to crash {@code packageName} {@code count} times. If {@code count} is at least - * {@link PackageWatchdog#TRIGGER_FAILURE_COUNT}, watchdog crash detection will be triggered. - */ - static BroadcastReceiver sendCrashBroadcast(Context context, String packageName, int count) - throws InterruptedException, IOException { - BlockingQueue<Integer> crashQueue = new SynchronousQueue<>(); - IntentFilter crashCountFilter = new IntentFilter(); - crashCountFilter.addAction("com.android.tests.rollback.CRASH"); - crashCountFilter.addCategory(Intent.CATEGORY_DEFAULT); - - BroadcastReceiver crashCountReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - try { - // Sleep long enough for packagewatchdog to be notified of crash - Thread.sleep(1000); - // Kill app and close AppErrorDialog - ActivityManager am = context.getSystemService(ActivityManager.class); - am.killBackgroundProcesses(packageName); - // Allow another package launch - crashQueue.put(intent.getIntExtra("count", 0)); - } catch (InterruptedException e) { - fail("Failed to communicate with test app"); - } - } - }; - context.registerReceiver(crashCountReceiver, crashCountFilter); - - do { - launchPackage(packageName); - } while(crashQueue.take() < count); - return crashCountReceiver; - } -} diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java index 1a29c4c11457..00bd4cf388ce 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java @@ -16,26 +16,30 @@ package com.android.tests.rollback; -import static com.android.tests.rollback.RollbackTestUtils.assertRollbackInfoEquals; -import static com.android.tests.rollback.RollbackTestUtils.getUniqueRollbackInfoForPackage; +import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat; +import static com.android.cts.rollback.lib.RollbackUtils.getUniqueRollbackInfoForPackage; + +import static com.google.common.truth.Truth.assertThat; import android.Manifest; -import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.pm.VersionedPackage; +import android.content.pm.PackageInstaller; +import android.content.pm.PackageManager; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; +import android.os.storage.StorageManager; +import android.provider.DeviceConfig; -import androidx.test.InstrumentationRegistry; +import androidx.test.platform.app.InstrumentationRegistry; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import com.android.cts.install.lib.Install; +import com.android.cts.install.lib.InstallUtils; +import com.android.cts.install.lib.LocalIntentSender; +import com.android.cts.install.lib.TestApp; +import com.android.cts.install.lib.Uninstall; +import com.android.cts.rollback.lib.Rollback; +import com.android.cts.rollback.lib.RollbackUtils; +import com.android.internal.R; import org.junit.After; import org.junit.Before; @@ -43,6 +47,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.io.File; +import java.util.concurrent.TimeUnit; + /** * Tests for rollback of staged installs. * <p> @@ -53,24 +60,20 @@ import org.junit.runners.JUnit4; */ @RunWith(JUnit4.class) public class StagedRollbackTest { - - private static final String TAG = "RollbackTest"; - private static final String TEST_APP_A = "com.android.tests.rollback.testapp.A"; - private static final String TEST_APP_A_V1 = "RollbackTestAppAv1.apk"; - private static final String TEST_APP_A_CRASHING_V2 = "RollbackTestAppACrashingV2.apk"; - private static final String NETWORK_STACK_CONNECTOR_CLASS = - "android.net.INetworkStackConnector"; + private static final String PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT = + "watchdog_trigger_failure_count"; /** * Adopts common shell permissions needed for rollback tests. */ @Before public void adoptShellPermissions() { - RollbackTestUtils.adoptShellPermissionIdentity( + InstallUtils.adoptShellPermissionIdentity( Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES, Manifest.permission.TEST_MANAGE_ROLLBACKS, - Manifest.permission.KILL_BACKGROUND_PROCESSES); + Manifest.permission.FORCE_STOP_PACKAGES, + Manifest.permission.WRITE_DEVICE_CONFIG); } /** @@ -78,7 +81,7 @@ public class StagedRollbackTest { */ @After public void dropShellPermissions() { - RollbackTestUtils.dropShellPermissionIdentity(); + InstallUtils.dropShellPermissionIdentity(); } /** @@ -86,18 +89,15 @@ public class StagedRollbackTest { * Enable rollback phase. */ @Test - public void testBadApkOnlyEnableRollback() throws Exception { - RollbackTestUtils.uninstall(TEST_APP_A); - assertEquals(-1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); - - RollbackTestUtils.install(TEST_APP_A_V1, false); - assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); - RollbackTestUtils.processUserData(TEST_APP_A); + public void testBadApkOnly_Phase1() throws Exception { + Uninstall.packages(TestApp.A); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); - RollbackTestUtils.installStaged(true, TEST_APP_A_CRASHING_V2); + Install.single(TestApp.A1).commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + InstallUtils.processUserData(TestApp.A); - // At this point, the host test driver will reboot the device and run - // testBadApkOnlyConfirmEnableRollback(). + Install.single(TestApp.ACrashing2).setEnableRollback().setStaged().commit(); } /** @@ -105,102 +105,387 @@ public class StagedRollbackTest { * Confirm that rollback was successfully enabled. */ @Test - public void testBadApkOnlyConfirmEnableRollback() throws Exception { - assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); - RollbackTestUtils.processUserData(TEST_APP_A); + public void testBadApkOnly_Phase2() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + InstallUtils.processUserData(TestApp.A); - RollbackManager rm = RollbackTestUtils.getRollbackManager(); + RollbackManager rm = RollbackUtils.getRollbackManager(); RollbackInfo rollback = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TEST_APP_A); - assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback); - assertTrue(rollback.isStaged()); + rm.getAvailableRollbacks(), TestApp.A); + assertThat(rollback).isNotNull(); + assertThat(rollback).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1)); + assertThat(rollback.isStaged()).isTrue(); - // At this point, the host test driver will run - // testBadApkOnlyTriggerRollback(). + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, + PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT, + Integer.toString(5), false); + RollbackUtils.sendCrashBroadcast(TestApp.A, 4); + // Sleep for a while to make sure we don't trigger rollback + Thread.sleep(TimeUnit.SECONDS.toMillis(30)); } /** * Test rollbacks of staged installs involving only apks with bad update. - * Trigger rollback phase. This is expected to fail due to watchdog - * rebooting the test out from under it. + * Trigger rollback phase. + */ + @Test + public void testBadApkOnly_Phase3() throws Exception { + // One more crash to trigger rollback + RollbackUtils.sendCrashBroadcast(TestApp.A, 1); + } + + /** + * Test rollbacks of staged installs involving only apks. + * Confirm rollback phase. + */ + @Test + public void testBadApkOnly_Phase4() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + InstallUtils.processUserData(TestApp.A); + + RollbackManager rm = RollbackUtils.getRollbackManager(); + RollbackInfo rollback = getUniqueRollbackInfoForPackage( + rm.getRecentlyCommittedRollbacks(), TestApp.A); + assertThat(rollback).isNotNull(); + assertThat(rollback).packagesContainsExactly( + Rollback.from(TestApp.A2).to(TestApp.A1)); + assertThat(rollback).causePackagesContainsExactly(TestApp.ACrashing2); + assertThat(rollback).isStaged(); + assertThat(rollback.getCommittedSessionId()).isNotEqualTo(-1); + } + + /** + * Stage install an apk with rollback that will be later triggered by unattributable crash. */ @Test - public void testBadApkOnlyTriggerRollback() throws Exception { - BroadcastReceiver crashCountReceiver = null; - Context context = InstrumentationRegistry.getContext(); - RollbackManager rm = RollbackTestUtils.getRollbackManager(); + public void testNativeWatchdogTriggersRollback_Phase1() throws Exception { + Uninstall.packages(TestApp.A); + Install.single(TestApp.A1).commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - try { - // Crash TEST_APP_A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback - crashCountReceiver = RollbackTestUtils.sendCrashBroadcast(context, TEST_APP_A, 5); - } finally { - if (crashCountReceiver != null) { - context.unregisterReceiver(crashCountReceiver); - } - } + Install.single(TestApp.A2).setEnableRollback().setStaged().commit(); + } - // We expect the device to be rebooted automatically. Wait for that to - // happen. At that point, the host test driver will wait for the - // device to come back up and run testApkOnlyConfirmRollback(). - Thread.sleep(30 * 1000); + /** + * Verify the rollback is available. + */ + @Test + public void testNativeWatchdogTriggersRollback_Phase2() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + RollbackManager rm = RollbackUtils.getRollbackManager(); + assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), + TestApp.A)).isNotNull(); + } - fail("watchdog did not trigger reboot"); + /** + * Verify the rollback is committed after crashing. + */ + @Test + public void testNativeWatchdogTriggersRollback_Phase3() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + RollbackManager rm = RollbackUtils.getRollbackManager(); + assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), + TestApp.A)).isNotNull(); } /** - * Test rollbacks of staged installs involving only apks. - * Confirm rollback phase. + * Stage install an apk with rollback that will be later triggered by unattributable crash. */ @Test - public void testBadApkOnlyConfirmRollback() throws Exception { - assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); - RollbackTestUtils.processUserData(TEST_APP_A); + public void testNativeWatchdogTriggersRollbackForAll_Phase1() throws Exception { + Uninstall.packages(TestApp.A); + Install.single(TestApp.A1).commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - RollbackManager rm = RollbackTestUtils.getRollbackManager(); + Install.single(TestApp.A2).setEnableRollback().setStaged().commit(); + } + + /** + * Verify the rollback is available and then install another package with rollback. + */ + @Test + public void testNativeWatchdogTriggersRollbackForAll_Phase2() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + RollbackManager rm = RollbackUtils.getRollbackManager(); + assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), + TestApp.A)).isNotNull(); + + // Install another package with rollback + Uninstall.packages(TestApp.B); + Install.single(TestApp.B1).commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); + + Install.single(TestApp.B2).setEnableRollback().setStaged().commit(); + } + + /** + * Verify the rollbacks are available. + */ + @Test + public void testNativeWatchdogTriggersRollbackForAll_Phase3() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); + RollbackManager rm = RollbackUtils.getRollbackManager(); + assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), + TestApp.A)).isNotNull(); + assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), + TestApp.B)).isNotNull(); + } + + /** + * Verify the rollbacks are committed after crashing. + */ + @Test + public void testNativeWatchdogTriggersRollbackForAll_Phase4() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); + RollbackManager rm = RollbackUtils.getRollbackManager(); + assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), + TestApp.A)).isNotNull(); + assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), + TestApp.B)).isNotNull(); + } + + @Test + public void testPreviouslyAbandonedRollbacks_Phase1() throws Exception { + Uninstall.packages(TestApp.A); + Install.single(TestApp.A1).commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + + int sessionId = Install.single(TestApp.A2).setStaged().setEnableRollback().commit(); + PackageInstaller pi = InstrumentationRegistry.getInstrumentation().getContext() + .getPackageManager().getPackageInstaller(); + pi.abandonSession(sessionId); + + // Remove the first intent sender result, so that the next staged install session does not + // erroneously think that it has itself been abandoned. + // TODO(b/136260017): Restructure LocalIntentSender to negate the need for this step. + LocalIntentSender.getIntentSenderResult(); + Install.single(TestApp.A2).setStaged().setEnableRollback().commit(); + } + + @Test + public void testPreviouslyAbandonedRollbacks_Phase2() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + InstallUtils.processUserData(TestApp.A); + + RollbackManager rm = RollbackUtils.getRollbackManager(); RollbackInfo rollback = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TEST_APP_A); - assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback, new VersionedPackage(TEST_APP_A, 2)); - assertTrue(rollback.isStaged()); - assertNotEquals(-1, rollback.getCommittedSessionId()); + rm.getAvailableRollbacks(), TestApp.A); + RollbackUtils.rollback(rollback.getRollbackId()); } @Test - public void resetNetworkStack() throws Exception { - RollbackManager rm = RollbackTestUtils.getRollbackManager(); - String networkStack = getNetworkStackPackageName(); + public void testPreviouslyAbandonedRollbacks_Phase3() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + InstallUtils.processUserData(TestApp.A); + Uninstall.packages(TestApp.A); + } - rm.expireRollbackForPackage(networkStack); - RollbackTestUtils.uninstall(networkStack); + private static String getModuleMetadataPackageName() { + return InstrumentationRegistry.getInstrumentation().getContext() + .getResources().getString(R.string.config_defaultModuleMetadataProvider); + } + + @Test + public void testRollbackWhitelistedApp_Phase1() throws Exception { + // Remove available rollbacks + String pkgName = getModuleMetadataPackageName(); + RollbackUtils.getRollbackManager().expireRollbackForPackage(pkgName); + assertThat(RollbackUtils.getAvailableRollback(pkgName)).isNull(); + + // Overwrite existing permissions. We don't want TEST_MANAGE_ROLLBACKS which allows us + // to enable rollback for any app + InstallUtils.adoptShellPermissionIdentity( + Manifest.permission.INSTALL_PACKAGES, + Manifest.permission.MANAGE_ROLLBACKS); + + // Re-install a whitelisted app with rollbacks enabled + String filePath = InstrumentationRegistry.getInstrumentation().getContext() + .getPackageManager().getPackageInfo(pkgName, 0).applicationInfo.sourceDir; + TestApp app = new TestApp("ModuleMetadata", pkgName, -1, false, new File(filePath)); + Install.single(app).setStaged().setEnableRollback() + .addInstallFlags(PackageManager.INSTALL_REPLACE_EXISTING).commit(); + } + + @Test + public void testRollbackWhitelistedApp_Phase2() throws Exception { + assertThat(RollbackUtils.getAvailableRollback(getModuleMetadataPackageName())).isNotNull(); + } + + @Test + public void testRollbackDataPolicy_Phase1() throws Exception { + Uninstall.packages(TestApp.A, TestApp.B); + Install.multi(TestApp.A1, TestApp.B1).commit(); + // Write user data version = 1 + InstallUtils.processUserData(TestApp.A); + InstallUtils.processUserData(TestApp.B); + + Install a2 = Install.single(TestApp.A2).setStaged() + .setEnableRollback(PackageManager.RollbackDataPolicy.WIPE); + Install b2 = Install.single(TestApp.B2).setStaged() + .setEnableRollback(PackageManager.RollbackDataPolicy.RESTORE); + Install.multi(a2, b2).setEnableRollback().setStaged().commit(); + } + + @Test + public void testRollbackDataPolicy_Phase2() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); + // Write user data version = 2 + InstallUtils.processUserData(TestApp.A); + InstallUtils.processUserData(TestApp.B); + + RollbackInfo info = RollbackUtils.getAvailableRollback(TestApp.A); + RollbackUtils.rollback(info.getRollbackId()); + } + + @Test + public void testRollbackDataPolicy_Phase3() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); + // Read user data version from userdata.txt + // A's user data version is -1 for user data is wiped. + // B's user data version is 1 as rollback committed. + assertThat(InstallUtils.getUserDataVersion(TestApp.A)).isEqualTo(-1); + assertThat(InstallUtils.getUserDataVersion(TestApp.B)).isEqualTo(1); + } + + @Test + public void testCleanUp() throws Exception { + // testNativeWatchdogTriggersRollback will fail if multiple staged sessions are + // committed on a device which doesn't support checkpoint. Let's clean up all rollbacks + // so there is only one rollback to commit when testing native crashes. + RollbackManager rm = RollbackUtils.getRollbackManager(); + rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream()) + .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage); + rm.getRecentlyCommittedRollbacks().stream().flatMap(info -> info.getPackages().stream()) + .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage); + assertThat(rm.getAvailableRollbacks()).isEmpty(); + assertThat(rm.getRecentlyCommittedRollbacks()).isEmpty(); + } + + private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; + private static final TestApp TEST_APEX_WITH_APK_V1 = new TestApp("TestApexWithApkV1", + APK_IN_APEX_TESTAPEX_NAME, 1, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v1.apex"); + private static final TestApp TEST_APEX_WITH_APK_V2 = new TestApp("TestApexWithApkV2", + APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v2.apex"); + private static final TestApp TEST_APEX_WITH_APK_V2_CRASHING = new TestApp( + "TestApexWithApkV2Crashing", APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true, + APK_IN_APEX_TESTAPEX_NAME + "_v2Crashing.apex"); + + @Test + public void testRollbackApexWithApk_Phase1() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + InstallUtils.processUserData(TestApp.A); - assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), - networkStack)); + int sessionId = Install.single(TEST_APEX_WITH_APK_V2).setStaged().setEnableRollback() + .commit(); + InstallUtils.waitForSessionReady(sessionId); } @Test - public void assertNetworkStackRollbackAvailable() throws Exception { - RollbackManager rm = RollbackTestUtils.getRollbackManager(); - assertNotNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), - getNetworkStackPackageName())); + public void testRollbackApexWithApk_Phase2() throws Exception { + assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + InstallUtils.processUserData(TestApp.A); + + RollbackInfo available = RollbackUtils.getAvailableRollback(APK_IN_APEX_TESTAPEX_NAME); + assertThat(available).isStaged(); + assertThat(available).packagesContainsExactly( + Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1), + Rollback.from(TestApp.A, 0).to(TestApp.A1)); + + RollbackUtils.rollback(available.getRollbackId(), TEST_APEX_WITH_APK_V2); + RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId()); + assertThat(committed).isNotNull(); + assertThat(committed).isStaged(); + assertThat(committed).packagesContainsExactly( + Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1), + Rollback.from(TestApp.A, 0).to(TestApp.A1)); + assertThat(committed).causePackagesContainsExactly(TEST_APEX_WITH_APK_V2); + assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1); + + // Note: The app is not rolled back until after the rollback is staged + // and the device has been rebooted. + InstallUtils.waitForSessionReady(committed.getCommittedSessionId()); + assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2); } @Test - public void assertNetworkStackRollbackCommitted() throws Exception { - RollbackManager rm = RollbackTestUtils.getRollbackManager(); - assertNotNull(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), - getNetworkStackPackageName())); + public void testRollbackApexWithApk_Phase3() throws Exception { + assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(1); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + InstallUtils.processUserData(TestApp.A); + } + + /** + * Installs an apex with an apk that can crash. + */ + @Test + public void testRollbackApexWithApkCrashing_Phase1() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + int sessionId = Install.single(TEST_APEX_WITH_APK_V2_CRASHING).setStaged() + .setEnableRollback().commit(); + InstallUtils.waitForSessionReady(sessionId); } + /** + * Verifies rollback has been enabled successfully. Then makes TestApp.A crash. + */ @Test - public void assertNoNetworkStackRollbackCommitted() throws Exception { - RollbackManager rm = RollbackTestUtils.getRollbackManager(); - assertNull(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), - getNetworkStackPackageName())); + public void testRollbackApexWithApkCrashing_Phase2() throws Exception { + assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + + RollbackInfo available = RollbackUtils.getAvailableRollback(APK_IN_APEX_TESTAPEX_NAME); + assertThat(available).isStaged(); + assertThat(available).packagesContainsExactly( + Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1), + Rollback.from(TestApp.A, 0).to(TestApp.A1)); + + // Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback + RollbackUtils.sendCrashBroadcast(TestApp.A, 5); } - private String getNetworkStackPackageName() { - Intent intent = new Intent(NETWORK_STACK_CONNECTOR_CLASS); - ComponentName comp = intent.resolveSystemService( - InstrumentationRegistry.getContext().getPackageManager(), 0); - return comp.getPackageName(); + @Test + public void testRollbackApexWithApkCrashing_Phase3() throws Exception { + assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(1); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + } + + @Test + public void testRollbackApexDataDirectories_Phase1() throws Exception { + int sessionId = Install.single(TEST_APEX_WITH_APK_V2).setStaged().setEnableRollback() + .commit(); + InstallUtils.waitForSessionReady(sessionId); + } + + @Test + public void testRollbackApexDataDirectories_Phase2() throws Exception { + RollbackInfo available = RollbackUtils.getAvailableRollback(APK_IN_APEX_TESTAPEX_NAME); + + RollbackUtils.rollback(available.getRollbackId(), TEST_APEX_WITH_APK_V2); + RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId()); + + // Note: The app is not rolled back until after the rollback is staged + // and the device has been rebooted. + InstallUtils.waitForSessionReady(committed.getCommittedSessionId()); + } + + @Test + public void isCheckpointSupported() { + Context context = InstrumentationRegistry.getInstrumentation().getContext(); + StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); + assertThat(sm.isCheckpointSupported()).isTrue(); + } + + @Test + public void hasMainlineModule() throws Exception { + String pkgName = getModuleMetadataPackageName(); + boolean existed = InstrumentationRegistry.getInstrumentation().getContext() + .getPackageManager().getModuleInfo(pkgName, 0) != null; + assertThat(existed).isTrue(); } } diff --git a/tests/RollbackTest/StagedRollbackTest.xml b/tests/RollbackTest/StagedRollbackTest.xml index 2750d3765c20..83fef8e0a04b 100644 --- a/tests/RollbackTest/StagedRollbackTest.xml +++ b/tests/RollbackTest/StagedRollbackTest.xml @@ -19,6 +19,12 @@ <option name="cleanup-apks" value="true" /> <option name="test-file-name" value="RollbackTest.apk" /> </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" /> + <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" /> + <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" /> + <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" /> + </target_preparer> <test class="com.android.tradefed.testtype.HostTest" > <option name="class" value="com.android.tests.rollback.host.StagedRollbackTest" /> </test> diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java index 4315514cb7a5..9169ef517bf7 100644 --- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java +++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java @@ -16,24 +16,42 @@ package com.android.tests.rollback.host; +import static com.android.tests.rollback.host.WatchdogEventLogger.watchdogEventOccurred; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; -import com.android.ddmlib.Log.LogLevel; -import com.android.tradefed.log.LogUtil.CLog; +import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.IFileEntry; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; +import com.android.tradefed.util.CommandResult; +import com.android.tradefed.util.CommandStatus; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.File; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + /** * Runs the staged rollback tests. + * + * TODO(gavincorkery): Support the verification of logging parents in Watchdog metrics. */ @RunWith(DeviceJUnit4ClassRunner.class) public class StagedRollbackTest extends BaseHostJUnit4Test { + private static final int NATIVE_CRASHES_THRESHOLD = 5; + /** * Runs the given phase of a test by calling into the device. * Throws an exception if the test phase fails. @@ -46,125 +64,461 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { phase)); } + private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; + private static final String TESTAPP_A = "com.android.cts.install.lib.testapp.A"; + + private static final String TEST_SUBDIR = "/subdir/"; + + private static final String TEST_FILENAME_1 = "test_file.txt"; + private static final String TEST_STRING_1 = "hello this is a test"; + private static final String TEST_FILENAME_2 = "another_file.txt"; + private static final String TEST_STRING_2 = "this is a different file"; + private static final String TEST_FILENAME_3 = "also.xyz"; + private static final String TEST_STRING_3 = "also\n a\n test\n string"; + private static final String TEST_FILENAME_4 = "one_more.test"; + private static final String TEST_STRING_4 = "once more unto the test"; + + private static final String REASON_APP_CRASH = "REASON_APP_CRASH"; + private static final String REASON_NATIVE_CRASH = "REASON_NATIVE_CRASH"; + + private static final String ROLLBACK_INITIATE = "ROLLBACK_INITIATE"; + private static final String ROLLBACK_BOOT_TRIGGERED = "ROLLBACK_BOOT_TRIGGERED"; + private static final String ROLLBACK_SUCCESS = "ROLLBACK_SUCCESS"; + + private WatchdogEventLogger mLogger = new WatchdogEventLogger(); + @Before public void setUp() throws Exception { - // Disconnect internet so we can test network health triggered rollbacks - getDevice().executeShellCommand("svc wifi disable"); - getDevice().executeShellCommand("svc data disable"); + deleteFiles("/system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex", + "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex"); + runPhase("testCleanUp"); + mLogger.start(getDevice()); } @After public void tearDown() throws Exception { - // Reconnect internet after testing network health triggered rollbacks - getDevice().executeShellCommand("svc wifi enable"); - getDevice().executeShellCommand("svc data enable"); + mLogger.stop(); + runPhase("testCleanUp"); + deleteFiles("/system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex", + "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex", + apexDataDirDeSys(APK_IN_APEX_TESTAPEX_NAME) + "*", + apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + "*"); + } + + /** + * Deletes files and reboots the device if necessary. + * @param files the paths of files which might contain wildcards + */ + private void deleteFiles(String... files) throws Exception { + boolean found = false; + for (String file : files) { + CommandResult result = getDevice().executeShellV2Command("ls " + file); + if (result.getStatus() == CommandStatus.SUCCESS) { + found = true; + break; + } + } + + if (found) { + if (!getDevice().isAdbRoot()) { + getDevice().enableAdbRoot(); + } + getDevice().remountSystemWritable(); + for (String file : files) { + getDevice().executeShellCommand("rm -rf " + file); + } + getDevice().reboot(); + } } /** * Tests watchdog triggered staged rollbacks involving only apks. */ @Test - @Ignore("b/139175593 flaky test") public void testBadApkOnly() throws Exception { - runPhase("testBadApkOnlyEnableRollback"); + runPhase("testBadApkOnly_Phase1"); getDevice().reboot(); - runPhase("testBadApkOnlyConfirmEnableRollback"); - try { - // This is expected to fail due to the device being rebooted out - // from underneath the test. If this fails for reasons other than - // the device reboot, those failures should result in failure of - // the testApkOnlyConfirmRollback phase. - CLog.logAndDisplay(LogLevel.INFO, "testBadApkOnlyTriggerRollback is expected to fail"); - runPhase("testBadApkOnlyTriggerRollback"); - } catch (AssertionError e) { - // AssertionError is expected. - } + runPhase("testBadApkOnly_Phase2"); + + // Trigger rollback and wait for reboot to happen + runPhase("testBadApkOnly_Phase3"); + assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(2))); getDevice().waitForDeviceAvailable(); - runPhase("testBadApkOnlyConfirmRollback"); + runPhase("testBadApkOnly_Phase4"); + + List<String> watchdogEvents = mLogger.getWatchdogLoggingEvents(); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null, + REASON_APP_CRASH, TESTAPP_A)); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null, + null, null)); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_SUCCESS, null, null, null)); } - /** - * Tests failed network health check triggers watchdog staged rollbacks. - */ @Test - public void testNetworkFailedRollback() throws Exception { - // Remove available rollbacks and uninstall NetworkStack on /data/ - runPhase("resetNetworkStack"); - // Reduce health check deadline - getDevice().executeShellCommand("device_config put rollback " - + "watchdog_request_timeout_millis 300000"); - // Simulate re-installation of new NetworkStack with rollbacks enabled - getDevice().executeShellCommand("pm install -r --staged --enable-rollback " - + "/system/priv-app/NetworkStack/NetworkStack.apk"); - - // Sleep to allow writes to disk before reboot - Thread.sleep(5000); + public void testNativeWatchdogTriggersRollback() throws Exception { + runPhase("testNativeWatchdogTriggersRollback_Phase1"); + // Reboot device to activate staged package getDevice().reboot(); + + runPhase("testNativeWatchdogTriggersRollback_Phase2"); + + // crash system_server enough times to trigger a rollback + crashProcess("system_server", NATIVE_CRASHES_THRESHOLD); + + // Rollback should be committed automatically now. + // Give time for rollback to be committed. This could take a while, + // because we need all of the following to happen: + // 1. system_server comes back up and boot completes. + // 2. Rollback health observer detects updatable crashing signal. + // 3. Staged rollback session becomes ready. + // 4. Device actually reboots. + // So we give a generous timeout here. + assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5))); getDevice().waitForDeviceAvailable(); - // Verify rollback was enabled - runPhase("assertNetworkStackRollbackAvailable"); + // verify rollback committed + runPhase("testNativeWatchdogTriggersRollback_Phase3"); - // Sleep for < health check deadline - Thread.sleep(5000); - // Verify rollback was not executed before health check deadline - runPhase("assertNoNetworkStackRollbackCommitted"); - try { - // This is expected to fail due to the device being rebooted out - // from underneath the test. If this fails for reasons other than - // the device reboot, those failures should result in failure of - // the assertNetworkStackExecutedRollback phase. - CLog.logAndDisplay(LogLevel.INFO, "Sleep and expect to fail while sleeping"); - // Sleep for > health check deadline - Thread.sleep(260000); - } catch (AssertionError e) { - // AssertionError is expected. - } + List<String> watchdogEvents = mLogger.getWatchdogLoggingEvents(); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null, + REASON_NATIVE_CRASH, null)); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null, + null, null)); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_SUCCESS, null, null, null)); + } + + @Test + public void testNativeWatchdogTriggersRollbackForAll() throws Exception { + // This test requires committing multiple staged rollbacks + assumeTrue(isCheckpointSupported()); + + // Install a package with rollback enabled. + runPhase("testNativeWatchdogTriggersRollbackForAll_Phase1"); + getDevice().reboot(); + // Once previous staged install is applied, install another package + runPhase("testNativeWatchdogTriggersRollbackForAll_Phase2"); + getDevice().reboot(); + + // Verify the new staged install has also been applied successfully. + runPhase("testNativeWatchdogTriggersRollbackForAll_Phase3"); + + // crash system_server enough times to trigger a rollback + crashProcess("system_server", NATIVE_CRASHES_THRESHOLD); + + // Rollback should be committed automatically now. + // Give time for rollback to be committed. This could take a while, + // because we need all of the following to happen: + // 1. system_server comes back up and boot completes. + // 2. Rollback health observer detects updatable crashing signal. + // 3. Staged rollback session becomes ready. + // 4. Device actually reboots. + // So we give a generous timeout here. + assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5))); getDevice().waitForDeviceAvailable(); - // Verify rollback was executed after health check deadline - runPhase("assertNetworkStackRollbackCommitted"); + + // verify all available rollbacks have been committed + runPhase("testNativeWatchdogTriggersRollbackForAll_Phase4"); + + List<String> watchdogEvents = mLogger.getWatchdogLoggingEvents(); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null, + REASON_NATIVE_CRASH, null)); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null, + null, null)); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_SUCCESS, null, null, null)); } /** - * Tests passed network health check does not trigger watchdog staged rollbacks. + * Tests rolling back user data where there are multiple rollbacks for that package. */ @Test - public void testNetworkPassedDoesNotRollback() throws Exception { - // Remove available rollbacks and uninstall NetworkStack on /data/ - runPhase("resetNetworkStack"); - // Reduce health check deadline, here unlike the network failed case, we use - // a longer deadline because joining a network can take a much longer time for - // reasons external to the device than 'not joining' - getDevice().executeShellCommand("device_config put rollback " - + "watchdog_request_timeout_millis 300000"); - // Simulate re-installation of new NetworkStack with rollbacks enabled - getDevice().executeShellCommand("pm install -r --staged --enable-rollback " - + "/system/priv-app/NetworkStack/NetworkStack.apk"); - - // Sleep to allow writes to disk before reboot - Thread.sleep(5000); - // Reboot device to activate staged package + public void testPreviouslyAbandonedRollbacks() throws Exception { + runPhase("testPreviouslyAbandonedRollbacks_Phase1"); getDevice().reboot(); - getDevice().waitForDeviceAvailable(); + runPhase("testPreviouslyAbandonedRollbacks_Phase2"); + getDevice().reboot(); + runPhase("testPreviouslyAbandonedRollbacks_Phase3"); + } + + /** + * Tests we can enable rollback for a whitelisted app. + */ + @Test + public void testRollbackWhitelistedApp() throws Exception { + assumeTrue(hasMainlineModule()); + runPhase("testRollbackWhitelistedApp_Phase1"); + getDevice().reboot(); + runPhase("testRollbackWhitelistedApp_Phase2"); + } + + @Test + public void testRollbackDataPolicy() throws Exception { + runPhase("testRollbackDataPolicy_Phase1"); + getDevice().reboot(); + runPhase("testRollbackDataPolicy_Phase2"); + getDevice().reboot(); + runPhase("testRollbackDataPolicy_Phase3"); + } - // Verify rollback was enabled - runPhase("assertNetworkStackRollbackAvailable"); + /** + * Tests that userdata of apk-in-apex is restored when apex is rolled back. + */ + @Test + public void testRollbackApexWithApk() throws Exception { + getDevice().uninstallPackage("com.android.cts.install.lib.testapp.A"); + pushTestApex(); + runPhase("testRollbackApexWithApk_Phase1"); + getDevice().reboot(); + runPhase("testRollbackApexWithApk_Phase2"); + getDevice().reboot(); + runPhase("testRollbackApexWithApk_Phase3"); + } - // Connect to internet so network health check passes - getDevice().executeShellCommand("svc wifi enable"); - getDevice().executeShellCommand("svc data enable"); + /** + * Tests that RollbackPackageHealthObserver is observing apk-in-apex. + */ + @Test + public void testRollbackApexWithApkCrashing() throws Exception { + getDevice().uninstallPackage("com.android.cts.install.lib.testapp.A"); + pushTestApex(); - // Wait for device available because emulator device may restart after turning - // on mobile data + // Install an apex with apk that crashes + runPhase("testRollbackApexWithApkCrashing_Phase1"); + getDevice().reboot(); + // Verify apex was installed and then crash the apk + runPhase("testRollbackApexWithApkCrashing_Phase2"); + // Wait for crash to trigger rollback + assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5))); getDevice().waitForDeviceAvailable(); + // Verify rollback occurred due to crash of apk-in-apex + runPhase("testRollbackApexWithApkCrashing_Phase3"); + + List<String> watchdogEvents = mLogger.getWatchdogLoggingEvents(); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null, + REASON_APP_CRASH, TESTAPP_A)); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null, + null, null)); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_SUCCESS, null, null, null)); + } - // Sleep for > health check deadline - Thread.sleep(310000); - // Verify rollback was not executed after health check deadline - runPhase("assertNoNetworkStackRollbackCommitted"); + /** + * Tests that data in DE_sys apex data directory is restored when apex is rolled back. + */ + @Test + public void testRollbackApexDataDirectories_DeSys() throws Exception { + List<String> before = getSnapshotDirectories("/data/misc/apexrollback"); + pushTestApex(); + + // Push files to apex data directory + String oldFilePath1 = apexDataDirDeSys(APK_IN_APEX_TESTAPEX_NAME) + "/" + TEST_FILENAME_1; + String oldFilePath2 = + apexDataDirDeSys(APK_IN_APEX_TESTAPEX_NAME) + TEST_SUBDIR + TEST_FILENAME_2; + assertTrue(getDevice().pushString(TEST_STRING_1, oldFilePath1)); + assertTrue(getDevice().pushString(TEST_STRING_2, oldFilePath2)); + + // Install new version of the APEX with rollback enabled + runPhase("testRollbackApexDataDirectories_Phase1"); + getDevice().reboot(); + + // Replace files in data directory + getDevice().deleteFile(oldFilePath1); + getDevice().deleteFile(oldFilePath2); + String newFilePath3 = apexDataDirDeSys(APK_IN_APEX_TESTAPEX_NAME) + "/" + TEST_FILENAME_3; + String newFilePath4 = + apexDataDirDeSys(APK_IN_APEX_TESTAPEX_NAME) + TEST_SUBDIR + TEST_FILENAME_4; + assertTrue(getDevice().pushString(TEST_STRING_3, newFilePath3)); + assertTrue(getDevice().pushString(TEST_STRING_4, newFilePath4)); + + // Roll back the APEX + runPhase("testRollbackApexDataDirectories_Phase2"); + getDevice().reboot(); + + // Verify that old files have been restored and new files are gone + assertEquals(TEST_STRING_1, getDevice().pullFileContents(oldFilePath1)); + assertEquals(TEST_STRING_2, getDevice().pullFileContents(oldFilePath2)); + assertNull(getDevice().pullFile(newFilePath3)); + assertNull(getDevice().pullFile(newFilePath4)); + + // Verify snapshots are deleted after restoration + List<String> after = getSnapshotDirectories("/data/misc/apexrollback"); + // Only check directories newly created during the test + after.removeAll(before); + after.forEach(dir -> assertDirectoryIsEmpty(dir)); + } + + /** + * Tests that data in DE (user) apex data directory is restored when apex is rolled back. + */ + @Test + public void testRollbackApexDataDirectories_DeUser() throws Exception { + List<String> before = getSnapshotDirectories("/data/misc_de/0/apexrollback"); + pushTestApex(); + + // Push files to apex data directory + String oldFilePath1 = apexDataDirDeUser( + APK_IN_APEX_TESTAPEX_NAME, 0) + "/" + TEST_FILENAME_1; + String oldFilePath2 = + apexDataDirDeUser(APK_IN_APEX_TESTAPEX_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_2; + assertTrue(getDevice().pushString(TEST_STRING_1, oldFilePath1)); + assertTrue(getDevice().pushString(TEST_STRING_2, oldFilePath2)); + + // Install new version of the APEX with rollback enabled + runPhase("testRollbackApexDataDirectories_Phase1"); + getDevice().reboot(); + + // Replace files in data directory + getDevice().deleteFile(oldFilePath1); + getDevice().deleteFile(oldFilePath2); + String newFilePath3 = + apexDataDirDeUser(APK_IN_APEX_TESTAPEX_NAME, 0) + "/" + TEST_FILENAME_3; + String newFilePath4 = + apexDataDirDeUser(APK_IN_APEX_TESTAPEX_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_4; + assertTrue(getDevice().pushString(TEST_STRING_3, newFilePath3)); + assertTrue(getDevice().pushString(TEST_STRING_4, newFilePath4)); + + // Roll back the APEX + runPhase("testRollbackApexDataDirectories_Phase2"); + getDevice().reboot(); + + // Verify that old files have been restored and new files are gone + assertEquals(TEST_STRING_1, getDevice().pullFileContents(oldFilePath1)); + assertEquals(TEST_STRING_2, getDevice().pullFileContents(oldFilePath2)); + assertNull(getDevice().pullFile(newFilePath3)); + assertNull(getDevice().pullFile(newFilePath4)); + + // Verify snapshots are deleted after restoration + List<String> after = getSnapshotDirectories("/data/misc_de/0/apexrollback"); + // Only check directories newly created during the test + after.removeAll(before); + after.forEach(dir -> assertDirectoryIsEmpty(dir)); + } + + /** + * Tests that data in CE apex data directory is restored when apex is rolled back. + */ + @Test + public void testRollbackApexDataDirectories_Ce() throws Exception { + List<String> before = getSnapshotDirectories("/data/misc_ce/0/apexrollback"); + pushTestApex(); + + // Push files to apex data directory + String oldFilePath1 = apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + "/" + TEST_FILENAME_1; + String oldFilePath2 = + apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_2; + assertTrue(getDevice().pushString(TEST_STRING_1, oldFilePath1)); + assertTrue(getDevice().pushString(TEST_STRING_2, oldFilePath2)); + + // Install new version of the APEX with rollback enabled + runPhase("testRollbackApexDataDirectories_Phase1"); + getDevice().reboot(); + + // Replace files in data directory + getDevice().deleteFile(oldFilePath1); + getDevice().deleteFile(oldFilePath2); + String newFilePath3 = apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + "/" + TEST_FILENAME_3; + String newFilePath4 = + apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_4; + assertTrue(getDevice().pushString(TEST_STRING_3, newFilePath3)); + assertTrue(getDevice().pushString(TEST_STRING_4, newFilePath4)); + + // Roll back the APEX + runPhase("testRollbackApexDataDirectories_Phase2"); + getDevice().reboot(); + + // Verify that old files have been restored and new files are gone + assertEquals(TEST_STRING_1, getDevice().pullFileContents(oldFilePath1)); + assertEquals(TEST_STRING_2, getDevice().pullFileContents(oldFilePath2)); + assertNull(getDevice().pullFile(newFilePath3)); + assertNull(getDevice().pullFile(newFilePath4)); + + // Verify snapshots are deleted after restoration + List<String> after = getSnapshotDirectories("/data/misc_ce/0/apexrollback"); + // Only check directories newly created during the test + after.removeAll(before); + after.forEach(dir -> assertDirectoryIsEmpty(dir)); + } + + private void pushTestApex() throws Exception { + CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild()); + final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex"; + final File apex = buildHelper.getTestFile(fileName); + if (!getDevice().isAdbRoot()) { + getDevice().enableAdbRoot(); + } + getDevice().remountSystemWritable(); + assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName)); + getDevice().reboot(); + } + + private static String apexDataDirDeSys(String apexName) { + return String.format("/data/misc/apexdata/%s", apexName); + } + + private static String apexDataDirDeUser(String apexName, int userId) { + return String.format("/data/misc_de/%d/apexdata/%s", userId, apexName); + } + + private static String apexDataDirCe(String apexName, int userId) { + return String.format("/data/misc_ce/%d/apexdata/%s", userId, apexName); + } + + private List<String> getSnapshotDirectories(String baseDir) { + try { + return getDevice().getFileEntry(baseDir).getChildren(false) + .stream().filter(entry -> entry.getName().matches("\\d+(-prerestore)?")) + .map(entry -> entry.getFullPath()) + .collect(Collectors.toList()); + } catch (Exception e) { + // Return an empty list if any error + return Collections.EMPTY_LIST; + } + } + + private void assertDirectoryIsEmpty(String path) { + try { + IFileEntry file = getDevice().getFileEntry(path); + assertTrue("Not a directory: " + path, file.isDirectory()); + assertTrue("Directory not empty: " + path, file.getChildren(false).isEmpty()); + } catch (DeviceNotAvailableException e) { + fail("Can't access directory: " + path); + } + } + + private void crashProcess(String processName, int numberOfCrashes) throws Exception { + String pid = ""; + String lastPid = "invalid"; + for (int i = 0; i < numberOfCrashes; ++i) { + // This condition makes sure before we kill the process, the process is running AND + // the last crash was finished. + while ("".equals(pid) || lastPid.equals(pid)) { + pid = getDevice().executeShellCommand("pidof " + processName); + } + getDevice().executeShellCommand("kill " + pid); + lastPid = pid; + } + } + + private boolean isCheckpointSupported() throws Exception { + try { + runPhase("isCheckpointSupported"); + return true; + } catch (AssertionError ignore) { + return false; + } + } + + /** + * True if this build has mainline modules installed. + */ + private boolean hasMainlineModule() throws Exception { + try { + runPhase("hasMainlineModule"); + return true; + } catch (AssertionError ignore) { + return false; + } } } diff --git a/tests/RollbackTest/TEST_MAPPING b/tests/RollbackTest/TEST_MAPPING index 6be93a0a199b..0f4c4603f9b4 100644 --- a/tests/RollbackTest/TEST_MAPPING +++ b/tests/RollbackTest/TEST_MAPPING @@ -5,6 +5,12 @@ }, { "name": "StagedRollbackTest" + }, + { + "name": "NetworkStagedRollbackTest" + }, + { + "name": "MultiUserRollbackTest" } ] } diff --git a/tests/RollbackTest/TestApp/Bv2.xml b/tests/RollbackTest/TestApp/Bv2.xml deleted file mode 100644 index bd3e6133f6f6..000000000000 --- a/tests/RollbackTest/TestApp/Bv2.xml +++ /dev/null @@ -1,35 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2018 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.tests.rollback.testapp.B" - android:versionCode="2" - android:versionName="2.0" > - - - <uses-sdk android:minSdkVersion="19" /> - - <application android:label="Rollback Test App B v2"> - <receiver android:name="com.android.tests.rollback.testapp.ProcessUserData" - android:exported="true" /> - <activity android:name="com.android.tests.rollback.testapp.MainActivity"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/tests/RollbackTest/TestApp/res_v3/values-anydpi/values.xml b/tests/RollbackTest/TestApp/res_v3/values-anydpi/values.xml deleted file mode 100644 index f2d8992bee37..000000000000 --- a/tests/RollbackTest/TestApp/res_v3/values-anydpi/values.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2019 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<resources> - <integer name="split_version">3</integer> -</resources> diff --git a/tests/RollbackTest/TestApp/res_v3/values/values.xml b/tests/RollbackTest/TestApp/res_v3/values/values.xml deleted file mode 100644 index 968168a4bf06..000000000000 --- a/tests/RollbackTest/TestApp/res_v3/values/values.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2019 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<resources> - <integer name="app_version">3</integer> - <integer name="split_version">0</integer> -</resources> diff --git a/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java b/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java deleted file mode 100644 index 97958acde21b..000000000000 --- a/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tests.rollback.testapp; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Bundle; - -/** - * A crashing test app for testing apk rollback support. - */ -public class CrashingMainActivity extends Activity { - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - incrementCountAndBroadcast(); - throw new RuntimeException("Intended force crash"); - } - - private void incrementCountAndBroadcast() { - SharedPreferences preferences = getSharedPreferences("prefs", Context.MODE_PRIVATE); - SharedPreferences.Editor editor = preferences.edit(); - int count = preferences.getInt("crash_count", 0); - editor.putInt("crash_count", ++count).commit(); - - Intent intent = new Intent("com.android.tests.rollback.CRASH"); - intent.putExtra("count", count); - sendBroadcast(intent); - } -} diff --git a/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/ProcessUserData.java b/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/ProcessUserData.java deleted file mode 100644 index 38c658e795aa..000000000000 --- a/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/ProcessUserData.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tests.rollback.testapp; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Scanner; - -/** - * A broadcast reciever to check for and update user app data version - * compatibility. - */ -public class ProcessUserData extends BroadcastReceiver { - - private static final String TAG = "RollbackTestApp"; - - /** - * Exception thrown in case of issue with user data. - */ - public static class UserDataException extends Exception { - public UserDataException(String message) { - super(message); - } - - public UserDataException(String message, Throwable cause) { - super(message, cause); - } - } - - @Override - public void onReceive(Context context, Intent intent) { - try { - processUserData(context); - setResultCode(1); - } catch (UserDataException e) { - setResultCode(0); - setResultData(e.getMessage()); - } - } - - /** - * Update the app's user data version to match the app version. - * - * @param context The application context. - * @throws UserDataException in case of problems with app user data. - */ - public void processUserData(Context context) throws UserDataException { - Resources res = context.getResources(); - String packageName = context.getPackageName(); - - int appVersionId = res.getIdentifier("app_version", "integer", packageName); - int appVersion = res.getInteger(appVersionId); - - int splitVersionId = res.getIdentifier("split_version", "integer", packageName); - int splitVersion = res.getInteger(splitVersionId); - - // Make sure the app version and split versions are compatible. - if (appVersion != splitVersion) { - throw new UserDataException("Split version " + splitVersion - + " does not match app version " + appVersion); - } - - // Read the version of the app's user data and ensure it is compatible - // with our version of the application. - File versionFile = new File(context.getFilesDir(), "version.txt"); - try { - Scanner s = new Scanner(versionFile); - int userDataVersion = s.nextInt(); - s.close(); - - if (userDataVersion > appVersion) { - throw new UserDataException("User data is from version " + userDataVersion - + ", which is not compatible with this version " + appVersion - + " of the RollbackTestApp"); - } - } catch (FileNotFoundException e) { - // No problem. This is a fresh install of the app or the user data - // has been wiped. - } - - // Record the current version of the app in the user data. - try { - PrintWriter pw = new PrintWriter(versionFile); - pw.println(appVersion); - pw.close(); - } catch (IOException e) { - throw new UserDataException("Unable to write user data.", e); - } - } -} diff --git a/tests/RollbackTest/lib/src/com/android/tests/rollback/host/WatchdogEventLogger.java b/tests/RollbackTest/lib/src/com/android/tests/rollback/host/WatchdogEventLogger.java new file mode 100644 index 000000000000..88731504eafe --- /dev/null +++ b/tests/RollbackTest/lib/src/com/android/tests/rollback/host/WatchdogEventLogger.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tests.rollback.host; + +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.device.LogcatReceiver; +import com.android.tradefed.result.InputStreamSource; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +public class WatchdogEventLogger { + private LogcatReceiver mReceiver; + + public void start(ITestDevice device) { + mReceiver = new LogcatReceiver(device, "logcat -s WatchdogRollbackLogger", + device.getOptions().getMaxLogcatDataSize(), 0); + mReceiver.start(); + } + + public void stop() { + if (mReceiver != null) { + mReceiver.stop(); + mReceiver.clear(); + } + } + + /** + * Returns a list of all Watchdog logging events which have occurred. + */ + public List<String> getWatchdogLoggingEvents() throws Exception { + try (InputStreamSource logcatStream = mReceiver.getLogcatData()) { + return getWatchdogLoggingEvents(logcatStream); + } + } + + private static List<String> getWatchdogLoggingEvents(InputStreamSource inputStreamSource) + throws Exception { + List<String> watchdogEvents = new ArrayList<>(); + InputStream inputStream = inputStreamSource.createInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + String line; + while ((line = reader.readLine()) != null) { + if (line.contains("Watchdog event occurred")) { + watchdogEvents.add(line); + } + } + return watchdogEvents; + } + + /** + * Returns whether a Watchdog event has occurred that matches the given criteria. + * + * Check the value of all non-null parameters against the list of Watchdog events that have + * occurred, and return {@code true} if an event exists which matches all criteria. + */ + public static boolean watchdogEventOccurred(List<String> loggingEvents, + String type, String logPackage, + String rollbackReason, String failedPackageName) throws Exception { + List<String> eventCriteria = new ArrayList<>(); + if (type != null) { + eventCriteria.add("type: " + type); + } + if (logPackage != null) { + eventCriteria.add("logPackage: " + logPackage); + } + if (rollbackReason != null) { + eventCriteria.add("rollbackReason: " + rollbackReason); + } + if (failedPackageName != null) { + eventCriteria.add("failedPackageName: " + failedPackageName); + } + for (String loggingEvent: loggingEvents) { + boolean matchesCriteria = true; + for (String criterion: eventCriteria) { + if (!loggingEvent.contains(criterion)) { + matchesCriteria = false; + } + } + if (matchesCriteria) { + return true; + } + } + return false; + } +} diff --git a/tests/RollbackTest/testdata/AndroidManifest.xml b/tests/RollbackTest/testdata/AndroidManifest.xml new file mode 100644 index 000000000000..f21ec899eb69 --- /dev/null +++ b/tests/RollbackTest/testdata/AndroidManifest.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.apex.apkrollback.test"> + <!-- APEX does not have classes.dex --> + <application android:hasCode="false" /> + <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="29"/> +</manifest> + diff --git a/tests/RollbackTest/testdata/manifest_v1.json b/tests/RollbackTest/testdata/manifest_v1.json new file mode 100644 index 000000000000..1762fc6764cf --- /dev/null +++ b/tests/RollbackTest/testdata/manifest_v1.json @@ -0,0 +1,4 @@ +{ + "name": "com.android.apex.apkrollback.test", + "version": 1 +} diff --git a/tests/RollbackTest/testdata/manifest_v2.json b/tests/RollbackTest/testdata/manifest_v2.json new file mode 100644 index 000000000000..c5127b9c3023 --- /dev/null +++ b/tests/RollbackTest/testdata/manifest_v2.json @@ -0,0 +1,4 @@ +{ + "name": "com.android.apex.apkrollback.test", + "version": 2 +} diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java index b185a26bb972..9324ba0b8b72 100644 --- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java +++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java @@ -250,10 +250,12 @@ public class SoundTriggerTestService extends Service { boolean status = mSoundTriggerUtil.addOrUpdateSoundModel(soundModel); if (status) { - postToast("Successfully loaded " + modelInfo.name + ", UUID=" + soundModel.uuid); + postToast("Successfully loaded " + modelInfo.name + ", UUID=" + + soundModel.getUuid()); setModelState(modelInfo, "Loaded"); } else { - postErrorToast("Failed to load " + modelInfo.name + ", UUID=" + soundModel.uuid + "!"); + postErrorToast("Failed to load " + modelInfo.name + ", UUID=" + + soundModel.getUuid() + "!"); setModelState(modelInfo, "Failed to load"); } } @@ -275,11 +277,12 @@ public class SoundTriggerTestService extends Service { modelInfo.detector = null; boolean status = mSoundTriggerUtil.deleteSoundModel(modelUuid); if (status) { - postToast("Successfully unloaded " + modelInfo.name + ", UUID=" + soundModel.uuid); + postToast("Successfully unloaded " + modelInfo.name + ", UUID=" + + soundModel.getUuid()); setModelState(modelInfo, "Unloaded"); } else { postErrorToast("Failed to unload " + - modelInfo.name + ", UUID=" + soundModel.uuid + "!"); + modelInfo.name + ", UUID=" + soundModel.getUuid() + "!"); setModelState(modelInfo, "Failed to unload"); } } @@ -299,7 +302,8 @@ public class SoundTriggerTestService extends Service { GenericSoundModel updated = createNewSoundModel(modelInfo); boolean status = mSoundTriggerUtil.addOrUpdateSoundModel(updated); if (status) { - postToast("Successfully reloaded " + modelInfo.name + ", UUID=" + modelInfo.modelUuid); + postToast("Successfully reloaded " + modelInfo.name + ", UUID=" + + modelInfo.modelUuid); setModelState(modelInfo, "Reloaded"); } else { postErrorToast("Failed to reload " @@ -321,7 +325,8 @@ public class SoundTriggerTestService extends Service { modelUuid, new DetectorCallback(modelInfo)); } - postMessage("Starting recognition for " + modelInfo.name + ", UUID=" + modelInfo.modelUuid); + postMessage("Starting recognition for " + modelInfo.name + ", UUID=" + + modelInfo.modelUuid); if (modelInfo.detector.startRecognition(modelInfo.captureAudio ? SoundTriggerDetector.RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO : SoundTriggerDetector.RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS)) { @@ -495,7 +500,8 @@ public class SoundTriggerTestService extends Service { if (properties.containsKey("dataFile")) { File modelDataFile = new File( - getFilesDir().getPath() + "/" + properties.getProperty("dataFile")); + getFilesDir().getPath() + "/" + + properties.getProperty("dataFile")); modelInfo.modelData = new byte[(int) modelDataFile.length()]; FileInputStream input = new FileInputStream(modelDataFile); input.read(modelInfo.modelData, 0, modelInfo.modelData.length); @@ -602,12 +608,14 @@ public class SoundTriggerTestService extends Service { FileOutputStream fos = null; try { fos = new FileOutputStream( new File( - getFilesDir() + File.separator + mModelInfo.name.replace(' ', '_') + - "_capture_" + format.getChannelCount() + "ch_" + - format.getSampleRate() + "hz_" + encoding + ".pcm")); + getFilesDir() + File.separator + + mModelInfo.name.replace(' ', '_') + + "_capture_" + format.getChannelCount() + "ch_" + + format.getSampleRate() + "hz_" + encoding + ".pcm")); } catch (IOException e) { Log.e(TAG, "Failed to open output for saving PCM data", e); - postErrorToast("Failed to open output for saving PCM data: " + e.getMessage()); + postErrorToast("Failed to open output for saving PCM data: " + + e.getMessage()); } // Inform the user we're recording. @@ -690,7 +698,8 @@ public class SoundTriggerTestService extends Service { AudioFormat format = event.getCaptureAudioFormat(); result = result + "AudioFormat: " + ((format == null) ? "null" : format.toString()); byte[] triggerAudio = event.getTriggerAudio(); - result = result + ", TriggerAudio: " + (triggerAudio == null ? "null" : triggerAudio.length); + result = result + ", TriggerAudio: " + + (triggerAudio == null ? "null" : triggerAudio.length); byte[] data = event.getData(); result = result + ", Data: " + (data == null ? "null" : data.length); if (data != null) { diff --git a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java b/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java index 65a3d8a337db..e36f398c53ea 100644 --- a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java +++ b/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java @@ -16,7 +16,6 @@ package android.hardware.soundtrigger; -import android.hardware.soundtrigger.SoundTrigger; import android.hardware.soundtrigger.SoundTrigger.ConfidenceLevel; import android.hardware.soundtrigger.SoundTrigger.Keyphrase; import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent; @@ -30,6 +29,7 @@ import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.SmallTest; import java.util.Arrays; +import java.util.Locale; import java.util.Random; import java.util.UUID; @@ -38,7 +38,8 @@ public class SoundTriggerTest extends InstrumentationTestCase { @SmallTest public void testKeyphraseParcelUnparcel_noUsers() throws Exception { - Keyphrase keyphrase = new Keyphrase(1, 0, "en-US", "hello", null); + Keyphrase keyphrase = new Keyphrase(1, 0, + Locale.forLanguageTag("en-US"), "hello", null); // Write to a parcel Parcel parcel = Parcel.obtain(); @@ -49,15 +50,16 @@ public class SoundTriggerTest extends InstrumentationTestCase { Keyphrase unparceled = Keyphrase.CREATOR.createFromParcel(parcel); // Verify that they are the same - assertEquals(keyphrase.id, unparceled.id); - assertNull(unparceled.users); - assertEquals(keyphrase.locale, unparceled.locale); - assertEquals(keyphrase.text, unparceled.text); + assertEquals(keyphrase.getId(), unparceled.getId()); + assertNull(unparceled.getUsers()); + assertEquals(keyphrase.getLocale(), unparceled.getLocale()); + assertEquals(keyphrase.getText(), unparceled.getText()); } @SmallTest public void testKeyphraseParcelUnparcel_zeroUsers() throws Exception { - Keyphrase keyphrase = new Keyphrase(1, 0, "en-US", "hello", new int[0]); + Keyphrase keyphrase = new Keyphrase(1, 0, + Locale.forLanguageTag("en-US"), "hello", new int[0]); // Write to a parcel Parcel parcel = Parcel.obtain(); @@ -68,15 +70,16 @@ public class SoundTriggerTest extends InstrumentationTestCase { Keyphrase unparceled = Keyphrase.CREATOR.createFromParcel(parcel); // Verify that they are the same - assertEquals(keyphrase.id, unparceled.id); - assertTrue(Arrays.equals(keyphrase.users, unparceled.users)); - assertEquals(keyphrase.locale, unparceled.locale); - assertEquals(keyphrase.text, unparceled.text); + assertEquals(keyphrase.getId(), unparceled.getId()); + assertTrue(Arrays.equals(keyphrase.getUsers(), unparceled.getUsers())); + assertEquals(keyphrase.getLocale(), unparceled.getLocale()); + assertEquals(keyphrase.getText(), unparceled.getText()); } @SmallTest public void testKeyphraseParcelUnparcel_pos() throws Exception { - Keyphrase keyphrase = new Keyphrase(1, 0, "en-US", "hello", new int[] {1, 2, 3, 4, 5}); + Keyphrase keyphrase = new Keyphrase(1, 0, + Locale.forLanguageTag("en-US"), "hello", new int[] {1, 2, 3, 4, 5}); // Write to a parcel Parcel parcel = Parcel.obtain(); @@ -87,17 +90,19 @@ public class SoundTriggerTest extends InstrumentationTestCase { Keyphrase unparceled = Keyphrase.CREATOR.createFromParcel(parcel); // Verify that they are the same - assertEquals(keyphrase.id, unparceled.id); - assertTrue(Arrays.equals(keyphrase.users, unparceled.users)); - assertEquals(keyphrase.locale, unparceled.locale); - assertEquals(keyphrase.text, unparceled.text); + assertEquals(keyphrase.getId(), unparceled.getId()); + assertTrue(Arrays.equals(keyphrase.getUsers(), unparceled.getUsers())); + assertEquals(keyphrase.getLocale(), unparceled.getLocale()); + assertEquals(keyphrase.getText(), unparceled.getText()); } @SmallTest public void testKeyphraseSoundModelParcelUnparcel_noData() throws Exception { Keyphrase[] keyphrases = new Keyphrase[2]; - keyphrases[0] = new Keyphrase(1, 0, "en-US", "hello", new int[] {0}); - keyphrases[1] = new Keyphrase(2, 0, "fr-FR", "there", new int[] {1, 2}); + keyphrases[0] = new Keyphrase(1, 0, Locale.forLanguageTag("en-US"), + "hello", new int[] {0}); + keyphrases[1] = new Keyphrase(2, 0, Locale.forLanguageTag("fr-FR"), + "there", new int[] {1, 2}); KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(), null, keyphrases); @@ -110,17 +115,19 @@ public class SoundTriggerTest extends InstrumentationTestCase { KeyphraseSoundModel unparceled = KeyphraseSoundModel.CREATOR.createFromParcel(parcel); // Verify that they are the same - assertEquals(ksm.uuid, unparceled.uuid); - assertNull(unparceled.data); - assertEquals(ksm.type, unparceled.type); - assertTrue(Arrays.equals(keyphrases, unparceled.keyphrases)); + assertEquals(ksm.getUuid(), unparceled.getUuid()); + assertNull(unparceled.getData()); + assertEquals(ksm.getType(), unparceled.getType()); + assertTrue(Arrays.equals(keyphrases, unparceled.getKeyphrases())); } @SmallTest public void testKeyphraseSoundModelParcelUnparcel_zeroData() throws Exception { Keyphrase[] keyphrases = new Keyphrase[2]; - keyphrases[0] = new Keyphrase(1, 0, "en-US", "hello", new int[] {0}); - keyphrases[1] = new Keyphrase(2, 0, "fr-FR", "there", new int[] {1, 2}); + keyphrases[0] = new Keyphrase(1, 0, Locale.forLanguageTag("en-US"), + "hello", new int[] {0}); + keyphrases[1] = new Keyphrase(2, 0, Locale.forLanguageTag("fr-FR"), + "there", new int[] {1, 2}); KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(), new byte[0], keyphrases); @@ -133,10 +140,10 @@ public class SoundTriggerTest extends InstrumentationTestCase { KeyphraseSoundModel unparceled = KeyphraseSoundModel.CREATOR.createFromParcel(parcel); // Verify that they are the same - assertEquals(ksm.uuid, unparceled.uuid); - assertEquals(ksm.type, unparceled.type); - assertTrue(Arrays.equals(ksm.keyphrases, unparceled.keyphrases)); - assertTrue(Arrays.equals(ksm.data, unparceled.data)); + assertEquals(ksm.getUuid(), unparceled.getUuid()); + assertEquals(ksm.getType(), unparceled.getType()); + assertTrue(Arrays.equals(ksm.getKeyphrases(), unparceled.getKeyphrases())); + assertTrue(Arrays.equals(ksm.getData(), unparceled.getData())); } @SmallTest @@ -155,10 +162,10 @@ public class SoundTriggerTest extends InstrumentationTestCase { KeyphraseSoundModel unparceled = KeyphraseSoundModel.CREATOR.createFromParcel(parcel); // Verify that they are the same - assertEquals(ksm.uuid, unparceled.uuid); - assertEquals(ksm.type, unparceled.type); - assertNull(unparceled.keyphrases); - assertTrue(Arrays.equals(ksm.data, unparceled.data)); + assertEquals(ksm.getUuid(), unparceled.getUuid()); + assertEquals(ksm.getType(), unparceled.getType()); + assertNull(unparceled.getKeyphrases()); + assertTrue(Arrays.equals(ksm.getData(), unparceled.getData())); } @SmallTest @@ -177,17 +184,19 @@ public class SoundTriggerTest extends InstrumentationTestCase { KeyphraseSoundModel unparceled = KeyphraseSoundModel.CREATOR.createFromParcel(parcel); // Verify that they are the same - assertEquals(ksm.uuid, unparceled.uuid); - assertEquals(ksm.type, unparceled.type); - assertTrue(Arrays.equals(ksm.keyphrases, unparceled.keyphrases)); - assertTrue(Arrays.equals(ksm.data, unparceled.data)); + assertEquals(ksm.getUuid(), unparceled.getUuid()); + assertEquals(ksm.getType(), unparceled.getType()); + assertTrue(Arrays.equals(ksm.getKeyphrases(), unparceled.getKeyphrases())); + assertTrue(Arrays.equals(ksm.getData(), unparceled.getData())); } @LargeTest public void testKeyphraseSoundModelParcelUnparcel_largeData() throws Exception { Keyphrase[] keyphrases = new Keyphrase[2]; - keyphrases[0] = new Keyphrase(1, 0, "en-US", "hello", new int[] {0}); - keyphrases[1] = new Keyphrase(2, 0, "fr-FR", "there", new int[] {1, 2}); + keyphrases[0] = new Keyphrase(1, 0, Locale.forLanguageTag("en-US"), + "hello", new int[] {0}); + keyphrases[1] = new Keyphrase(2, 0, Locale.forLanguageTag("fr-FR"), + "there", new int[] {1, 2}); byte[] data = new byte[200 * 1024]; mRandom.nextBytes(data); KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(), @@ -202,10 +211,10 @@ public class SoundTriggerTest extends InstrumentationTestCase { KeyphraseSoundModel unparceled = KeyphraseSoundModel.CREATOR.createFromParcel(parcel); // Verify that they are the same - assertEquals(ksm.uuid, unparceled.uuid); - assertEquals(ksm.type, unparceled.type); - assertTrue(Arrays.equals(ksm.data, unparceled.data)); - assertTrue(Arrays.equals(ksm.keyphrases, unparceled.keyphrases)); + assertEquals(ksm.getUuid(), unparceled.getUuid()); + assertEquals(ksm.getType(), unparceled.getType()); + assertTrue(Arrays.equals(ksm.getData(), unparceled.getData())); + assertTrue(Arrays.equals(ksm.getKeyphrases(), unparceled.getKeyphrases())); } @SmallTest diff --git a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/stubhal/GenericSoundModelTest.java b/tests/SoundTriggerTests/src/android/hardware/soundtrigger/stubhal/GenericSoundModelTest.java index c0583ceb2fab..2c3592c640bc 100644 --- a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/stubhal/GenericSoundModelTest.java +++ b/tests/SoundTriggerTests/src/android/hardware/soundtrigger/stubhal/GenericSoundModelTest.java @@ -17,7 +17,6 @@ package android.hardware.soundtrigger; import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; @@ -37,6 +36,8 @@ import android.test.suitebuilder.annotation.SmallTest; import com.android.internal.app.ISoundTriggerService; +import org.mockito.MockitoAnnotations; + import java.io.DataOutputStream; import java.net.InetAddress; import java.net.Socket; @@ -45,8 +46,6 @@ import java.util.HashSet; import java.util.Random; import java.util.UUID; -import org.mockito.MockitoAnnotations; - public class GenericSoundModelTest extends AndroidTestCase { static final int MSG_DETECTION_ERROR = -1; static final int MSG_DETECTION_RESUME = 0; @@ -96,11 +95,11 @@ public class GenericSoundModelTest extends AndroidTestCase { // Update sound model soundTriggerService.updateSoundModel(model); - loadedModelUuids.add(model.uuid); + loadedModelUuids.add(model.getUuid()); // Confirm it was updated GenericSoundModel returnedModel = - soundTriggerService.getSoundModel(new ParcelUuid(model.uuid)); + soundTriggerService.getSoundModel(new ParcelUuid(model.getUuid())); assertEquals(model, returnedModel); } @@ -110,15 +109,15 @@ public class GenericSoundModelTest extends AndroidTestCase { // Update sound model soundTriggerService.updateSoundModel(model); - loadedModelUuids.add(model.uuid); + loadedModelUuids.add(model.getUuid()); // Delete sound model - soundTriggerService.deleteSoundModel(new ParcelUuid(model.uuid)); - loadedModelUuids.remove(model.uuid); + soundTriggerService.deleteSoundModel(new ParcelUuid(model.getUuid())); + loadedModelUuids.remove(model.getUuid()); // Confirm it was deleted GenericSoundModel returnedModel = - soundTriggerService.getSoundModel(new ParcelUuid(model.uuid)); + soundTriggerService.getSoundModel(new ParcelUuid(model.getUuid())); assertEquals(null, returnedModel); } @@ -134,14 +133,14 @@ public class GenericSoundModelTest extends AndroidTestCase { // Update and start sound model recognition soundTriggerService.updateSoundModel(model); - loadedModelUuids.add(model.uuid); - int r = soundTriggerService.startRecognition(new ParcelUuid(model.uuid), spyCallback, + loadedModelUuids.add(model.getUuid()); + int r = soundTriggerService.startRecognition(new ParcelUuid(model.getUuid()), spyCallback, config); assertEquals("Could Not Start Recognition with code: " + r, android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r); // Stop recognition - r = soundTriggerService.stopRecognition(new ParcelUuid(model.uuid), spyCallback); + r = soundTriggerService.stopRecognition(new ParcelUuid(model.getUuid()), spyCallback); assertEquals("Could Not Stop Recognition with code: " + r, android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r); } @@ -158,13 +157,13 @@ public class GenericSoundModelTest extends AndroidTestCase { // Update and start sound model soundTriggerService.updateSoundModel(model); - loadedModelUuids.add(model.uuid); - soundTriggerService.startRecognition(new ParcelUuid(model.uuid), spyCallback, config); + loadedModelUuids.add(model.getUuid()); + soundTriggerService.startRecognition(new ParcelUuid(model.getUuid()), spyCallback, config); // Send trigger to stub HAL Socket socket = new Socket(InetAddress.getLocalHost(), 14035); DataOutputStream out = new DataOutputStream(socket.getOutputStream()); - out.writeBytes("trig " + model.uuid.toString() + "\r\n"); + out.writeBytes("trig " + model.getUuid().toString() + "\r\n"); out.flush(); socket.close(); @@ -227,11 +226,12 @@ public class GenericSoundModelTest extends AndroidTestCase { if (operation == 0 && modelInfo.status == STATUS_UNLOADED) { // Update and start sound model soundTriggerService.updateSoundModel(modelInfo.model); - loadedModelUuids.add(modelInfo.model.uuid); + loadedModelUuids.add(modelInfo.model.getUuid()); modelInfo.status = STATUS_LOADED; } else if (operation == 1 && modelInfo.status == STATUS_LOADED) { // Start the sound model - int r = soundTriggerService.startRecognition(new ParcelUuid(modelInfo.model.uuid), + int r = soundTriggerService.startRecognition(new ParcelUuid( + modelInfo.model.getUuid()), spyCallback, config); assertEquals("Could Not Start Recognition with code: " + r, android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r); @@ -240,7 +240,7 @@ public class GenericSoundModelTest extends AndroidTestCase { // Send trigger to stub HAL Socket socket = new Socket(InetAddress.getLocalHost(), 14035); DataOutputStream out = new DataOutputStream(socket.getOutputStream()); - out.writeBytes("trig " + modelInfo.model.uuid + "\r\n"); + out.writeBytes("trig " + modelInfo.model.getUuid() + "\r\n"); out.flush(); socket.close(); @@ -249,19 +249,20 @@ public class GenericSoundModelTest extends AndroidTestCase { reset(spyCallback); } else if (operation == 3 && modelInfo.status == STATUS_STARTED) { // Stop recognition - int r = soundTriggerService.stopRecognition(new ParcelUuid(modelInfo.model.uuid), + int r = soundTriggerService.stopRecognition(new ParcelUuid( + modelInfo.model.getUuid()), spyCallback); assertEquals("Could Not Stop Recognition with code: " + r, android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r); modelInfo.status = STATUS_LOADED; } else if (operation == 4 && modelInfo.status != STATUS_UNLOADED) { // Delete sound model - soundTriggerService.deleteSoundModel(new ParcelUuid(modelInfo.model.uuid)); - loadedModelUuids.remove(modelInfo.model.uuid); + soundTriggerService.deleteSoundModel(new ParcelUuid(modelInfo.model.getUuid())); + loadedModelUuids.remove(modelInfo.model.getUuid()); // Confirm it was deleted - GenericSoundModel returnedModel = - soundTriggerService.getSoundModel(new ParcelUuid(modelInfo.model.uuid)); + GenericSoundModel returnedModel = soundTriggerService.getSoundModel( + new ParcelUuid(modelInfo.model.getUuid())); assertEquals(null, returnedModel); modelInfo.status = STATUS_UNLOADED; } diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp new file mode 100644 index 000000000000..65d15a7dccaa --- /dev/null +++ b/tests/StagedInstallTest/Android.bp @@ -0,0 +1,31 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_test_helper_app { + name: "StagedInstallInternalTestApp", + manifest: "app/AndroidManifest.xml", + srcs: ["app/src/**/*.java"], + static_libs: ["androidx.test.rules", "cts-install-lib"], + test_suites: ["general-tests"], +} + +java_test_host { + name: "StagedInstallInternalTest", + srcs: ["src/**/*.java"], + libs: ["tradefed"], + static_libs: ["testng", "compatibility-tradefed", "frameworks-base-hostutils"], + test_suites: ["general-tests"], + test_config: "StagedInstallInternalTest.xml", +} + diff --git a/tests/StagedInstallTest/StagedInstallInternalTest.xml b/tests/StagedInstallTest/StagedInstallInternalTest.xml new file mode 100644 index 000000000000..1b8fa672fe38 --- /dev/null +++ b/tests/StagedInstallTest/StagedInstallInternalTest.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<configuration description="Runs the internal staged install tests"> + <option name="test-suite-tag" value="StagedInstallTest" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="StagedInstallInternalTestApp.apk" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" /> + <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" /> + <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" /> + <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" /> + </target_preparer> + <test class="com.android.tradefed.testtype.HostTest" > + <option name="class" + value="com.android.tests.stagedinstallinternal.host.StagedInstallInternalTest" /> + </test> +</configuration> diff --git a/tests/StagedInstallTest/TEST_MAPPING b/tests/StagedInstallTest/TEST_MAPPING new file mode 100644 index 000000000000..5a7a5a766b88 --- /dev/null +++ b/tests/StagedInstallTest/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "StagedInstallInternalTest" + } + ] +} diff --git a/tests/StagedInstallTest/app/AndroidManifest.xml b/tests/StagedInstallTest/app/AndroidManifest.xml new file mode 100644 index 000000000000..a678f1ec3691 --- /dev/null +++ b/tests/StagedInstallTest/app/AndroidManifest.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.tests.stagedinstallinternal" > + + <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> + <application> + <receiver android:name="com.android.cts.install.lib.LocalIntentSender" + android:exported="true" /> + <uses-library android:name="android.test.runner" /> + </application> + + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.tests.stagedinstallinternal" + android:label="StagedInstallInternal Test"/> + +</manifest> diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java new file mode 100644 index 000000000000..02597d548361 --- /dev/null +++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tests.stagedinstallinternal; + +import static com.android.cts.install.lib.InstallUtils.getPackageInstaller; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import android.Manifest; +import android.content.pm.PackageInstaller; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.cts.install.lib.Install; +import com.android.cts.install.lib.InstallUtils; +import com.android.cts.install.lib.TestApp; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.nio.file.Files; +import java.util.List; +import java.util.function.Consumer; + +@RunWith(JUnit4.class) +public class StagedInstallInternalTest { + + private static final String TAG = StagedInstallInternalTest.class.getSimpleName(); + + private File mTestStateFile = new File( + InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(), + "stagedinstall_state"); + + /** + * Adopts common shell permissions needed for staged install tests. + */ + @Before + public void adoptShellPermissions() { + InstallUtils.adoptShellPermissionIdentity( + Manifest.permission.INSTALL_PACKAGES, + Manifest.permission.DELETE_PACKAGES + ); + } + + /** + * Drops shell permissions needed for staged install tests. + */ + @After + public void dropShellPermissions() { + InstallUtils.dropShellPermissionIdentity(); + } + + // This is marked as @Test to take advantage of @Before/@After methods of this class. Actual + // purpose of this method to be called before and after each test case of + // com.android.test.stagedinstall.host.StagedInstallTest to reduce tests flakiness. + @Test + public void cleanUp() throws Exception { + Files.deleteIfExists(mTestStateFile.toPath()); + } + + @Test + public void testSystemServerRestartDoesNotAffectStagedSessions_Commit() throws Exception { + int sessionId = Install.single(TestApp.A1).setStaged().commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); + assertSessionReady(sessionId); + storeSessionId(sessionId); + } + + @Test + public void testSystemServerRestartDoesNotAffectStagedSessions_Verify() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); + int sessionId = retrieveLastSessionId(); + assertSessionReady(sessionId); + } + + private static void assertSessionReady(int sessionId) { + assertSessionState(sessionId, + (session) -> assertThat(session.isStagedSessionReady()).isTrue()); + } + + private static void assertSessionState( + int sessionId, Consumer<PackageInstaller.SessionInfo> assertion) { + PackageInstaller packageInstaller = getPackageInstaller(); + + List<PackageInstaller.SessionInfo> sessions = packageInstaller.getStagedSessions(); + boolean found = false; + for (PackageInstaller.SessionInfo session : sessions) { + if (session.getSessionId() == sessionId) { + assertion.accept(session); + found = true; + } + } + assertWithMessage("Expecting to find session in getStagedSession()") + .that(found).isTrue(); + + // Test also that getSessionInfo correctly returns the session. + PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId); + assertion.accept(sessionInfo); + } + + private void storeSessionId(int sessionId) throws Exception { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(mTestStateFile))) { + writer.write("" + sessionId); + } + } + + private int retrieveLastSessionId() throws Exception { + try (BufferedReader reader = new BufferedReader(new FileReader(mTestStateFile))) { + return Integer.parseInt(reader.readLine()); + } + } +} diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java new file mode 100644 index 000000000000..530f885e62bf --- /dev/null +++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tests.stagedinstallinternal.host; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertTrue; + +import com.android.ddmlib.Log; +import com.android.tests.rollback.host.AbandonSessionsRule; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; +import com.android.tradefed.util.ProcessInfo; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class StagedInstallInternalTest extends BaseHostJUnit4Test { + + private static final String TAG = StagedInstallInternalTest.class.getSimpleName(); + private static final long SYSTEM_SERVER_TIMEOUT_MS = 60 * 1000; + + @Rule + public AbandonSessionsRule mHostTestRule = new AbandonSessionsRule(this); + + /** + * Runs the given phase of a test by calling into the device. + * Throws an exception if the test phase fails. + * <p> + * For example, <code>runPhase("testApkOnlyEnableRollback");</code> + */ + private void runPhase(String phase) throws Exception { + assertTrue(runDeviceTests("com.android.tests.stagedinstallinternal", + "com.android.tests.stagedinstallinternal.StagedInstallInternalTest", + phase)); + } + + // We do not assert the success of cleanup phase since it might fail due to flaky reasons. + private void cleanUp() throws Exception { + try { + runDeviceTests("com.android.tests.stagedinstallinternal", + "com.android.tests.stagedinstallinternal.StagedInstallInternalTest", + "cleanUp"); + } catch (AssertionError e) { + Log.e(TAG, e); + } + } + + @Before + public void setUp() throws Exception { + cleanUp(); + } + + @After + public void tearDown() throws Exception { + cleanUp(); + } + + @Test + public void testSystemServerRestartDoesNotAffectStagedSessions() throws Exception { + runPhase("testSystemServerRestartDoesNotAffectStagedSessions_Commit"); + restartSystemServer(); + runPhase("testSystemServerRestartDoesNotAffectStagedSessions_Verify"); + } + + private void restartSystemServer() throws Exception { + // Restart the system server + long oldStartTime = getDevice().getProcessByName("system_server").getStartTime(); + + getDevice().enableAdbRoot(); // Need root to restart system server + assertThat(getDevice().executeShellCommand("am restart")).contains("Restart the system"); + getDevice().disableAdbRoot(); + + // Wait for new system server process to start + long start = System.currentTimeMillis(); + long newStartTime = oldStartTime; + while (System.currentTimeMillis() < start + SYSTEM_SERVER_TIMEOUT_MS) { + ProcessInfo newPs = getDevice().getProcessByName("system_server"); + if (newPs != null) { + newStartTime = newPs.getStartTime(); + if (newStartTime != oldStartTime) { + break; + } + } + Thread.sleep(500); + } + assertThat(newStartTime).isNotEqualTo(oldStartTime); + getDevice().waitForDeviceAvailable(); + } +} diff --git a/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java b/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java index cd04c2e197f9..3d72ee67a227 100644 --- a/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java +++ b/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java @@ -18,13 +18,13 @@ package com.android.statusbartest; import android.app.Notification; import android.app.NotificationManager; -import android.view.View; -import android.content.Intent; import android.app.PendingIntent; import android.app.StatusBarManager; +import android.content.Intent; import android.os.Handler; -import android.util.Log; import android.os.SystemClock; +import android.util.Log; +import android.view.View; import android.view.Window; import android.view.WindowManager; diff --git a/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionMeasuringActivity.java b/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionMeasuringActivity.java index 4771b6cfc750..e0a96689c191 100644 --- a/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionMeasuringActivity.java +++ b/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionMeasuringActivity.java @@ -23,7 +23,6 @@ import android.app.ActionBar; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManager.MemoryInfo; -import android.content.Context; import android.content.pm.PackageManager; import android.graphics.Color; import android.graphics.PixelFormat; @@ -531,8 +530,7 @@ public class SurfaceCompositionMeasuringActivity extends Activity implements OnC } private void detectRefreshRate() { - WindowManager wm = (WindowManager)getSystemService(Context.WINDOW_SERVICE); - mRefreshRate = wm.getDefaultDisplay().getRefreshRate(); + mRefreshRate = getDisplay().getRefreshRate(); if (mRefreshRate < MIN_REFRESH_RATE_SUPPORTED) throw new RuntimeException("Unsupported display refresh rate: " + mRefreshRate); mTargetFPS = mRefreshRate - 2.0f; diff --git a/tests/SurfaceControlViewHostTest/Android.bp b/tests/SurfaceControlViewHostTest/Android.bp new file mode 100644 index 000000000000..e4e060010eea --- /dev/null +++ b/tests/SurfaceControlViewHostTest/Android.bp @@ -0,0 +1,22 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +android_test { + name: "SurfaceControlViewHostTest", + srcs: ["**/*.java"], + platform_apis: true, + certificate: "platform", +} diff --git a/tests/RollbackTest/TestApp/Bv1.xml b/tests/SurfaceControlViewHostTest/AndroidManifest.xml index ca9c2ec47a20..ee95763453f7 100644 --- a/tests/RollbackTest/TestApp/Bv1.xml +++ b/tests/SurfaceControlViewHostTest/AndroidManifest.xml @@ -1,12 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2018 The Android Open Source Project +<!-- Copyright (C) 2019 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,21 +13,16 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.tests.rollback.testapp.B" - android:versionCode="1" - android:versionName="1.0" > - + package="com.android.test.viewembed"> - <uses-sdk android:minSdkVersion="19" /> - - <application android:label="Rollback Test App B v1"> - <receiver android:name="com.android.tests.rollback.testapp.ProcessUserData" - android:exported="true" /> - <activity android:name="com.android.tests.rollback.testapp.MainActivity"> + <application> + <activity android:name="SurfaceControlViewHostTest" android:label="View Embedding Test"> <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application> + + </manifest> diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java new file mode 100644 index 000000000000..3bc530975580 --- /dev/null +++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.viewembed; + +import android.app.Activity; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.os.Bundle; +import android.view.Gravity; +import android.view.SurfaceControl; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.WindowManager; +import android.view.SurfaceControlViewHost; +import android.widget.Button; +import android.widget.FrameLayout; + + +public class SurfaceControlViewHostTest extends Activity implements SurfaceHolder.Callback{ + SurfaceView mView; + SurfaceControlViewHost mVr; + + protected void onCreate(Bundle savedInstanceState) { + FrameLayout content = new FrameLayout(this); + super.onCreate(savedInstanceState); + mView = new SurfaceView(this); + content.addView(mView, new FrameLayout.LayoutParams( + 500, 500, Gravity.CENTER_HORIZONTAL | Gravity.TOP)); + setContentView(content); + + mView.setZOrderOnTop(true); + mView.getHolder().addCallback(this); + + addEmbeddedView(); + } + + void addEmbeddedView() { + mVr = new SurfaceControlViewHost(this, this.getDisplay(), + mView.getHostToken()); + + mView.setChildSurfacePackage(mVr.getSurfacePackage()); + + Button v = new Button(this); + v.setBackgroundColor(Color.BLUE); + v.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + v.setBackgroundColor(Color.RED); + } + }); + WindowManager.LayoutParams lp = + new WindowManager.LayoutParams(500, 500, WindowManager.LayoutParams.TYPE_APPLICATION, + 0, PixelFormat.OPAQUE); + mVr.setView(v, lp); + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + Canvas canvas = holder.lockCanvas(); + canvas.drawColor(Color.GREEN); + holder.unlockCanvasAndPost(canvas); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + } +} diff --git a/tests/WindowManagerStressTest/Android.bp b/tests/TaskOrganizerTest/Android.bp index 98749a7e4512..8a13dbc52c66 100644 --- a/tests/WindowManagerStressTest/Android.bp +++ b/tests/TaskOrganizerTest/Android.bp @@ -1,5 +1,5 @@ // -// Copyright (C) 2016 The Android Open Source Project +// Copyright (C) 2019 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,7 +15,8 @@ // android_test { - name: "WindowManagerStressTest", + name: "TaskOrganizerTest", srcs: ["**/*.java"], platform_apis: true, + certificate: "platform", } diff --git a/tests/TaskOrganizerTest/AndroidManifest.xml b/tests/TaskOrganizerTest/AndroidManifest.xml new file mode 100644 index 000000000000..a77d7ee80242 --- /dev/null +++ b/tests/TaskOrganizerTest/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 The Android Open Source Project + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.test.taskembed"> + <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /> + <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> + <application> + <service android:name=".TaskOrganizerPipTest" + android:exported="true"> + </service> + <activity android:name="TaskOrganizerMultiWindowTest" android:label="TaskOrganizer MW Test"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java new file mode 100644 index 000000000000..073ae30aaf1a --- /dev/null +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.taskembed; + +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; + +import android.app.Activity; +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.graphics.Rect; +import android.os.Bundle; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.SurfaceControl; +import android.view.SurfaceHolder; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.window.TaskOrganizer; +import android.window.WindowContainerTransaction; +import android.window.WindowContainerTransactionCallback; + +public class TaskOrganizerMultiWindowTest extends Activity { + static class SplitLayout extends LinearLayout implements View.OnTouchListener { + View mView1; + View mView2; + View mDividerView; + + public boolean onTouch(View v, MotionEvent e) { + if (e.getAction() != MotionEvent.ACTION_UP) { + return true; + } + + float x = e.getRawX(0); + float ratio = (float) x / (float) getWidth() ; + ratio = 1-ratio; + + LinearLayout.LayoutParams lp1 = + new LinearLayout.LayoutParams(0, + ViewGroup.LayoutParams.WRAP_CONTENT, ratio-0.02f); + LinearLayout.LayoutParams lp2 = + new LinearLayout.LayoutParams(0, + ViewGroup.LayoutParams.WRAP_CONTENT, 1-ratio-0.02f); + updateViewLayout(mView1, lp2); + updateViewLayout(mView2, lp1); + return true; + } + + SplitLayout(Context c, View v1, View v2) { + super(c); + LinearLayout.LayoutParams lp1 = + new LinearLayout.LayoutParams(0, + ViewGroup.LayoutParams.WRAP_CONTENT, 0.48f); + LinearLayout.LayoutParams lp3 = + new LinearLayout.LayoutParams(0, + ViewGroup.LayoutParams.WRAP_CONTENT, 0.48f); + LinearLayout.LayoutParams lp2 = + new LinearLayout.LayoutParams(0, + ViewGroup.LayoutParams.FILL_PARENT, 0.04f); + lp2.gravity = Gravity.CENTER; + + setWeightSum(1); + + mView1 = v1; + mView2 = v2; + addView(mView1, lp1); + + mDividerView = new View(getContext()); + mDividerView.setBackgroundColor(Color.BLACK); + addView(mDividerView, lp2); + mDividerView.setOnTouchListener(this); + + addView(mView2, lp3); + } + } + + class ResizingTaskView extends TaskView { + final Intent mIntent; + boolean launched = false; + ResizingTaskView(Context c, Intent i) { + super(c); + mIntent = i; + } + + @Override + public void surfaceChanged(SurfaceHolder h, int format, int width, int height) { + if (!launched) { + launchOrganizedActivity(mIntent, width, height); + launched = true; + } else { + resizeTask(width, height); + } + } + + void resizeTask(int width, int height) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setBounds(mWc, new Rect(0, 0, width, height)); + try { + mOrganizer.applySyncTransaction(wct, mOrganizer.mTransactionCallback); + } catch (Exception e) { + // Oh well + } + } + } + + private TaskView mTaskView1; + private TaskView mTaskView2; + private boolean mGotFirstTask = false; + + class Organizer extends TaskOrganizer { + private int receivedTransactions = 0; + SurfaceControl.Transaction mergedTransaction = new SurfaceControl.Transaction(); + WindowContainerTransactionCallback mTransactionCallback = + new WindowContainerTransactionCallback() { + @Override + public void onTransactionReady(int id, SurfaceControl.Transaction t) { + mergedTransaction.merge(t); + receivedTransactions++; + if (receivedTransactions == 2) { + mergedTransaction.apply(); + receivedTransactions = 0; + } + } + }; + + @Override + public void onTaskAppeared(ActivityManager.RunningTaskInfo ti, SurfaceControl leash) { + if (!mGotFirstTask) { + mTaskView1.reparentTask(ti.token, leash); + mGotFirstTask = true; + } else { + mTaskView2.reparentTask(ti.token, leash); + } + } + } + + private Organizer mOrganizer = new Organizer(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mOrganizer.registerOrganizer(WINDOWING_MODE_MULTI_WINDOW); + + mTaskView1 = new ResizingTaskView(this, makeSettingsIntent()); + mTaskView2 = new ResizingTaskView(this, makeContactsIntent()); + View splitView = new SplitLayout(this, mTaskView1, mTaskView2); + + setContentView(splitView); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mOrganizer.unregisterOrganizer(); + } + + private void addFlags(Intent intent) { + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION); + } + + private Intent makeSettingsIntent() { + Intent intent = new Intent(); + intent.setAction(android.provider.Settings.ACTION_SETTINGS); + addFlags(intent); + return intent; + } + + private Intent makeContactsIntent() { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_APP_CONTACTS); + addFlags(intent); + return intent; + } + + private Bundle makeLaunchOptions(int width, int height) { + ActivityOptions o = ActivityOptions.makeBasic(); + o.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + o.setLaunchBounds(new Rect(0, 0, width, height)); + return o.toBundle(); + } + + private void launchOrganizedActivity(Intent i, int width, int height) { + startActivity(i, makeLaunchOptions(width, height)); + } +} diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java new file mode 100644 index 000000000000..8fc5c5d78b60 --- /dev/null +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.taskembed; + +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; + +import android.app.ActivityManager; +import android.app.Service; +import android.content.Intent; +import android.graphics.Rect; +import android.os.IBinder; +import android.view.SurfaceControl; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.window.TaskOrganizer; +import android.window.WindowContainerTransaction; + +public class TaskOrganizerPipTest extends Service { + private static final int PIP_WIDTH = 640; + private static final int PIP_HEIGHT = 360; + + private TaskView mTaskView; + + class Organizer extends TaskOrganizer { + public void onTaskAppeared(ActivityManager.RunningTaskInfo ti, SurfaceControl leash) { + mTaskView.reparentTask(ti.token, leash); + + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.scheduleFinishEnterPip(ti.token, new Rect(0, 0, PIP_WIDTH, PIP_HEIGHT)); + applyTransaction(wct); + } + } + + private Organizer mOrganizer = new Organizer(); + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + + mOrganizer.registerOrganizer(WINDOWING_MODE_PINNED); + + final WindowManager.LayoutParams wlp = new WindowManager.LayoutParams(); + wlp.setTitle("TaskOrganizerPipTest"); + wlp.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + wlp.width = wlp.height = ViewGroup.LayoutParams.WRAP_CONTENT; + + FrameLayout layout = new FrameLayout(this); + ViewGroup.LayoutParams lp = + new ViewGroup.LayoutParams(PIP_WIDTH, PIP_HEIGHT); + mTaskView = new TaskView(this); + layout.addView(mTaskView, lp); + + WindowManager wm = getSystemService(WindowManager.class); + wm.addView(layout, wlp); + } + + @Override + public void onDestroy() { + super.onDestroy(); + mOrganizer.unregisterOrganizer(); + } +} diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java new file mode 100644 index 000000000000..208018c2543a --- /dev/null +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.taskembed; + +import android.content.Context; +import android.view.SurfaceControl; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.window.WindowContainerToken; + +/** + * Simple SurfaceView wrapper which registers a TaskOrganizer + * after it's Surface is ready. + */ +class TaskView extends SurfaceView implements SurfaceHolder.Callback { + WindowContainerToken mWc; + private SurfaceControl mLeash; + + private boolean mSurfaceCreated = false; + private boolean mNeedsReparent; + + TaskView(Context c) { + super(c); + getHolder().addCallback(this); + setZOrderOnTop(true); + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + mSurfaceCreated = true; + if (mNeedsReparent) { + mNeedsReparent = false; + reparentLeash(); + } + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + } + + void reparentTask(WindowContainerToken wc, SurfaceControl leash) { + mWc = wc; + mLeash = leash; + if (!mSurfaceCreated) { + mNeedsReparent = true; + } else { + reparentLeash(); + } + } + + private void reparentLeash() { + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + if (mLeash == null) { + return; + } + + t.reparent(mLeash, getSurfaceControl()) + .setPosition(mLeash, 0, 0) + .show(mLeash) + .apply(); + } +} diff --git a/tests/TelephonyCommonTests/Android.bp b/tests/TelephonyCommonTests/Android.bp new file mode 100644 index 000000000000..4f7569d4f451 --- /dev/null +++ b/tests/TelephonyCommonTests/Android.bp @@ -0,0 +1,49 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +android_test { + name: "TelephonyCommonTests", + srcs: [ + ":framework-telephony-common-sources", + "**/*.java", + ], + static_libs: [ + "mockito-target-extended", + "androidx.test.rules", + "truth-prebuilt", + "platform-test-annotations", + "androidx.core_core", + "androidx.fragment_fragment", + "androidx.test.ext.junit" + ], + + jni_libs: ["libdexmakerjvmtiagent"], + + // We need to rename SmsApplication to the test package or else it'll get clobbered by the + // hidden api checker + jarjar_rules: "jarjar-rules.txt", + + // Uncomment this and comment out the jarjar rule if you want to attach a debugger and step + // through the renamed classes. + // platform_apis: true, + + libs: [ + "android.test.runner", + "android.test.mock", + "android.test.base", + "unsupportedappusage", + ], +} diff --git a/tests/TelephonyCommonTests/AndroidManifest.xml b/tests/TelephonyCommonTests/AndroidManifest.xml new file mode 100644 index 000000000000..63f38c6af2cb --- /dev/null +++ b/tests/TelephonyCommonTests/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.internal.telephony.tests" + android:debuggable="true"> + + <application android:label="TelephonyCommonTests" + android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.internal.telephony.tests" + android:label="Telephony common tests" + android:debuggable="true"/> +</manifest> diff --git a/tests/TelephonyCommonTests/AndroidTest.xml b/tests/TelephonyCommonTests/AndroidTest.xml new file mode 100644 index 000000000000..e9fdabcaf136 --- /dev/null +++ b/tests/TelephonyCommonTests/AndroidTest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<configuration description="Runs Telephony Common Test Cases."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-instrumentation" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="TelephonyCommonTests.apk" /> + </target_preparer> + + <option name="test-tag" value="TelephonyCommonTests" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.internal.telephony.tests" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + </test> +</configuration> diff --git a/tests/TelephonyCommonTests/jarjar-rules.txt b/tests/TelephonyCommonTests/jarjar-rules.txt new file mode 100644 index 000000000000..4d1115f3c56c --- /dev/null +++ b/tests/TelephonyCommonTests/jarjar-rules.txt @@ -0,0 +1,3 @@ +rule com.android.internal.telephony.SmsApplication* com.android.internal.telephony.tests.SmsApplication@1 +rule android.telephony.PackageChangeReceiver* com.android.internal.telephony.tests.PackageChangeReceiver@1 +rule com.android.internal.os.BackgroundThread* com.android.internal.telephony.tests.BackgroundThread@1 diff --git a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java new file mode 100644 index 000000000000..83fd20803ed8 --- /dev/null +++ b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.tests; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNotNull; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.Manifest; +import android.app.AppOpsManager; +import android.app.role.RoleManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.net.Uri; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Telephony; +import android.telephony.TelephonyManager; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.internal.telephony.SmsApplication; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Unit tests for the {@link SmsApplication} utility class + */ +@RunWith(AndroidJUnit4.class) +public class SmsApplicationTest { + private static final ComponentName TEST_COMPONENT_NAME = + ComponentName.unflattenFromString("com.android.test/.TestSmsApp"); + private static final String MMS_RECEIVER_NAME = "TestMmsReceiver"; + private static final String RESPOND_VIA_SMS_NAME = "TestRespondViaSmsHandler"; + private static final String SEND_TO_NAME = "TestSendTo"; + private static final int SMS_APP_UID = 10001; + + private static final int FAKE_PHONE_UID = 10002; + private static final int FAKE_MMS_UID = 10003; + private static final int FAKE_BT_UID = 10004; + private static final int FAKE_TELEPHONY_PROVIDER_UID = 10005; + + private static final String[] APP_OPS_TO_CHECK = { + AppOpsManager.OPSTR_READ_SMS, + AppOpsManager.OPSTR_WRITE_SMS, + AppOpsManager.OPSTR_RECEIVE_SMS, + AppOpsManager.OPSTR_RECEIVE_WAP_PUSH, + AppOpsManager.OPSTR_SEND_SMS, + AppOpsManager.OPSTR_READ_CELL_BROADCASTS + }; + + private static final Set<String> SCHEMES_FOR_PREFERRED_APP = Arrays.stream(new String[]{ + "mms", + "mmsto", + "sms", + "smsto" + }).collect(Collectors.toSet()); + + @Mock private Context mContext; + @Mock private TelephonyManager mTelephonyManager; + @Mock private RoleManager mRoleManager; + @Mock private PackageManager mPackageManager; + @Mock private AppOpsManager mAppOpsManager; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager); + when(mContext.getSystemService(Context.ROLE_SERVICE)).thenReturn(mRoleManager); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mContext.getSystemService(RoleManager.class)).thenReturn(mRoleManager); + when(mContext.getSystemService(AppOpsManager.class)).thenReturn(mAppOpsManager); + when(mContext.createContextAsUser(isNotNull(), anyInt())).thenReturn(mContext); + + doAnswer(invocation -> getResolveInfosForIntent(invocation.getArgument(0))) + .when(mPackageManager) + .queryBroadcastReceiversAsUser(nullable(Intent.class), anyInt(), + nullable(UserHandle.class)); + doAnswer(invocation -> getResolveInfosForIntent(invocation.getArgument(0))) + .when(mPackageManager) + .queryIntentActivitiesAsUser(nullable(Intent.class), anyInt(), + nullable(UserHandle.class)); + doAnswer(invocation -> getResolveInfosForIntent(invocation.getArgument(0))) + .when(mPackageManager) + .queryIntentServicesAsUser(nullable(Intent.class), anyInt(), + nullable(UserHandle.class)); + + when(mTelephonyManager.isSmsCapable()).thenReturn(true); + when(mRoleManager.isRoleAvailable(RoleManager.ROLE_SMS)).thenReturn(true); + when(mRoleManager.getDefaultSmsPackage(anyInt())) + .thenReturn(TEST_COMPONENT_NAME.getPackageName()); + + for (String opStr : APP_OPS_TO_CHECK) { + when(mAppOpsManager.unsafeCheckOp( + opStr, SMS_APP_UID, TEST_COMPONENT_NAME.getPackageName())) + .thenReturn(AppOpsManager.MODE_ALLOWED); + } + } + + @Test + public void testGetDefaultSmsApplication() { + assertEquals(TEST_COMPONENT_NAME, + SmsApplication.getDefaultSmsApplicationAsUser(mContext, false, 0)); + } + + @Test + public void testGetDefaultSmsApplicationWithAppOpsFix() throws Exception { + when(mAppOpsManager.unsafeCheckOp(AppOpsManager.OPSTR_READ_SMS, SMS_APP_UID, + TEST_COMPONENT_NAME.getPackageName())) + .thenReturn(AppOpsManager.MODE_IGNORED); + setupPackageInfosForCoreApps(); + + assertEquals(TEST_COMPONENT_NAME, + SmsApplication.getDefaultSmsApplicationAsUser(mContext, true, 0)); + verify(mAppOpsManager, atLeastOnce()).setUidMode(AppOpsManager.OPSTR_READ_SMS, SMS_APP_UID, + AppOpsManager.MODE_ALLOWED); + } + + @Test + public void testPackageChanged() throws Exception { + setupPackageInfosForCoreApps(); + SmsApplication.initSmsPackageMonitor(mContext); + verify(mContext).createContextAsUser(eq(UserHandle.ALL), anyInt()); + ArgumentCaptor<BroadcastReceiver> captor = ArgumentCaptor.forClass(BroadcastReceiver.class); + verify(mContext).registerReceiver(captor.capture(), isNotNull(), + isNull(), nullable(Handler.class)); + BroadcastReceiver smsPackageMonitor = captor.getValue(); + + Intent packageChangedIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED); + packageChangedIntent.setData( + Uri.fromParts("package", TEST_COMPONENT_NAME.getPackageName(), null)); + smsPackageMonitor.onReceive(mContext, packageChangedIntent); + + ArgumentCaptor<IntentFilter> intentFilterCaptor = + ArgumentCaptor.forClass(IntentFilter.class); + verify(mPackageManager, times(SCHEMES_FOR_PREFERRED_APP.size())) + .replacePreferredActivity(intentFilterCaptor.capture(), + eq(IntentFilter.MATCH_CATEGORY_SCHEME + | IntentFilter.MATCH_ADJUSTMENT_NORMAL), + isNotNull(List.class), + eq(new ComponentName(TEST_COMPONENT_NAME.getPackageName(), SEND_TO_NAME))); + + Set<String> capturedSchemes = intentFilterCaptor.getAllValues().stream() + .map(intentFilter -> intentFilter.getDataScheme(0)) + .collect(Collectors.toSet()); + assertEquals(SCHEMES_FOR_PREFERRED_APP.size(), capturedSchemes.size()); + assertTrue(SCHEMES_FOR_PREFERRED_APP.containsAll(capturedSchemes)); + } + + private void setupPackageInfosForCoreApps() throws Exception { + PackageInfo phonePackageInfo = new PackageInfo(); + ApplicationInfo phoneApplicationInfo = new ApplicationInfo(); + phoneApplicationInfo.uid = FAKE_PHONE_UID; + phonePackageInfo.applicationInfo = phoneApplicationInfo; + when(mPackageManager.getPackageInfo(eq(SmsApplication.PHONE_PACKAGE_NAME), anyInt())) + .thenReturn(phonePackageInfo); + + PackageInfo mmsPackageInfo = new PackageInfo(); + ApplicationInfo mmsApplicationInfo = new ApplicationInfo(); + mmsApplicationInfo.uid = FAKE_MMS_UID; + mmsPackageInfo.applicationInfo = mmsApplicationInfo; + when(mPackageManager.getPackageInfo(eq(SmsApplication.MMS_SERVICE_PACKAGE_NAME), anyInt())) + .thenReturn(mmsPackageInfo); + + PackageInfo bluetoothPackageInfo = new PackageInfo(); + ApplicationInfo bluetoothApplicationInfo = new ApplicationInfo(); + bluetoothApplicationInfo.uid = FAKE_BT_UID; + bluetoothPackageInfo.applicationInfo = bluetoothApplicationInfo; + when(mPackageManager.getPackageInfo(eq(SmsApplication.BLUETOOTH_PACKAGE_NAME), anyInt())) + .thenReturn(bluetoothPackageInfo); + + PackageInfo telephonyProviderPackageInfo = new PackageInfo(); + ApplicationInfo telephonyProviderApplicationInfo = new ApplicationInfo(); + telephonyProviderApplicationInfo.uid = FAKE_TELEPHONY_PROVIDER_UID; + telephonyProviderPackageInfo.applicationInfo = telephonyProviderApplicationInfo; + when(mPackageManager.getPackageInfo( + eq(SmsApplication.TELEPHONY_PROVIDER_PACKAGE_NAME), anyInt())) + .thenReturn(telephonyProviderPackageInfo); + } + + private List<ResolveInfo> getResolveInfosForIntent(Intent intent) { + switch (intent.getAction()) { + case Telephony.Sms.Intents.SMS_DELIVER_ACTION: + return Collections.singletonList(makeSmsDeliverResolveInfo()); + case Telephony.Sms.Intents.WAP_PUSH_DELIVER_ACTION: + return Collections.singletonList(makeWapPushResolveInfo()); + case TelephonyManager.ACTION_RESPOND_VIA_MESSAGE: + return Collections.singletonList(makeRespondViaMessageResolveInfo()); + case Intent.ACTION_SENDTO: + return Collections.singletonList(makeSendToResolveInfo()); + } + return Collections.emptyList(); + } + + private ApplicationInfo makeSmsApplicationInfo() { + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.uid = SMS_APP_UID; + return applicationInfo; + } + + private ResolveInfo makeSmsDeliverResolveInfo() { + ResolveInfo info = new ResolveInfo(); + ActivityInfo activityInfo = new ActivityInfo(); + activityInfo.applicationInfo = makeSmsApplicationInfo(); + + activityInfo.permission = Manifest.permission.BROADCAST_SMS; + activityInfo.packageName = TEST_COMPONENT_NAME.getPackageName(); + activityInfo.name = TEST_COMPONENT_NAME.getClassName(); + + info.activityInfo = activityInfo; + return info; + } + + private ResolveInfo makeWapPushResolveInfo() { + ResolveInfo info = new ResolveInfo(); + ActivityInfo activityInfo = new ActivityInfo(); + + activityInfo.permission = Manifest.permission.BROADCAST_WAP_PUSH; + activityInfo.packageName = TEST_COMPONENT_NAME.getPackageName(); + activityInfo.name = MMS_RECEIVER_NAME; + + info.activityInfo = activityInfo; + return info; + } + + private ResolveInfo makeRespondViaMessageResolveInfo() { + ResolveInfo info = new ResolveInfo(); + ServiceInfo serviceInfo = new ServiceInfo(); + + serviceInfo.permission = Manifest.permission.SEND_RESPOND_VIA_MESSAGE; + serviceInfo.packageName = TEST_COMPONENT_NAME.getPackageName(); + serviceInfo.name = RESPOND_VIA_SMS_NAME; + + info.serviceInfo = serviceInfo; + return info; + } + + private ResolveInfo makeSendToResolveInfo() { + ResolveInfo info = new ResolveInfo(); + ActivityInfo activityInfo = new ActivityInfo(); + + activityInfo.packageName = TEST_COMPONENT_NAME.getPackageName(); + activityInfo.name = SEND_TO_NAME; + + info.activityInfo = activityInfo; + return info; + } +} diff --git a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java index ba77a74974d1..1664746f4636 100644 --- a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java +++ b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java @@ -73,6 +73,10 @@ class TouchLatencyView extends View implements View.OnTouchListener { mFps = 0; mLastFpsUpdate = 0; mFrameCount = 0; + + mDf = new DecimalFormat("fps: #.##"); + mDf.setRoundingMode(RoundingMode.HALF_UP); + Trace.endSection(); } @@ -181,9 +185,7 @@ class TouchLatencyView extends View implements View.OnTouchListener { // Draw the ball canvas.drawColor(BACKGROUND_COLOR); canvas.drawOval(left, top, right, bottom, getBallColor()); - DecimalFormat df = new DecimalFormat("fps: #.##"); - df.setRoundingMode(RoundingMode.HALF_UP); - canvas.drawText(df.format(mFps), width, 100, mTextPaint); + canvas.drawText(mDf.format(mFps), width, 100, mTextPaint); invalidate(); Trace.endSection(); @@ -220,6 +222,7 @@ class TouchLatencyView extends View implements View.OnTouchListener { private long mLastDrawNano, mLastFpsUpdate, mFrameCount; private float mFps; + private DecimalFormat mDf; } public class TouchLatencyActivity extends Activity { diff --git a/tests/UiBench/AndroidManifest.xml b/tests/UiBench/AndroidManifest.xml index c6b4a54f3b0b..dd255ef5233b 100644 --- a/tests/UiBench/AndroidManifest.xml +++ b/tests/UiBench/AndroidManifest.xml @@ -306,5 +306,14 @@ <category android:name="com.android.test.uibench.TEST" /> </intent-filter> </activity> + + <activity + android:name="WindowInsetsControllerActivity" + android:label="WindowInsetsControllerActivity" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="com.android.test.uibench.TEST" /> + </intent-filter> + </activity> </application> </manifest> diff --git a/tests/UiBench/res/drawable-nodpi/frantic.jpg b/tests/UiBench/res/drawable-nodpi/frantic.jpg Binary files differindex 4c623336e15b..856b41950d81 100644 --- a/tests/UiBench/res/drawable-nodpi/frantic.jpg +++ b/tests/UiBench/res/drawable-nodpi/frantic.jpg diff --git a/tests/UiBench/src/com/android/test/uibench/ClippedListActivity.java b/tests/UiBench/src/com/android/test/uibench/ClippedListActivity.java index 2bf6040351b8..e21dec32e7ca 100644 --- a/tests/UiBench/src/com/android/test/uibench/ClippedListActivity.java +++ b/tests/UiBench/src/com/android/test/uibench/ClippedListActivity.java @@ -15,27 +15,24 @@ */ package com.android.test.uibench; -import android.os.Bundle; import android.view.MenuItem; import android.widget.ArrayAdapter; import android.widget.ListAdapter; import androidx.appcompat.app.ActionBarDrawerToggle; -import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.core.view.GravityCompat; import androidx.drawerlayout.widget.DrawerLayout; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.ListFragment; + +import com.android.test.uibench.listview.CompatListActivity; import com.google.android.material.navigation.NavigationView; -public class ClippedListActivity extends AppCompatActivity +public class ClippedListActivity extends CompatListActivity implements NavigationView.OnNavigationItemSelectedListener { @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + protected void initializeActivity() { setContentView(R.layout.activity_navigation_drawer); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); @@ -48,15 +45,17 @@ public class ClippedListActivity extends AppCompatActivity NavigationView navigationView = findViewById(R.id.nav_view); navigationView.setNavigationItemSelectedListener(this); + } + + @Override + protected ListAdapter createListAdapter() { + return new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, + TextUtils.buildSimpleStringList(40)); + } - FragmentManager fm = getSupportFragmentManager(); - if (fm.findFragmentById(android.R.id.content) == null) { - ListFragment listFragment = new ListFragment(); - ListAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, - TextUtils.buildSimpleStringList(40)); - listFragment.setListAdapter(adapter); - fm.beginTransaction().add(R.id.app_bar_layout, listFragment).commit(); - } + @Override + protected int getListFragmentContainerViewId() { + return R.id.app_bar_layout; } @Override diff --git a/tests/UiBench/src/com/android/test/uibench/MainActivity.java b/tests/UiBench/src/com/android/test/uibench/MainActivity.java index 0a7aa4281b00..77a6c321acf1 100644 --- a/tests/UiBench/src/com/android/test/uibench/MainActivity.java +++ b/tests/UiBench/src/com/android/test/uibench/MainActivity.java @@ -19,13 +19,15 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Bundle; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.ListFragment; -import androidx.appcompat.app.AppCompatActivity; import android.view.View; +import android.widget.ListAdapter; import android.widget.ListView; import android.widget.SimpleAdapter; +import androidx.fragment.app.ListFragment; + +import com.android.test.uibench.listview.CompatListActivity; + import java.text.Collator; import java.util.ArrayList; import java.util.Collections; @@ -34,10 +36,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -public class MainActivity extends AppCompatActivity { +public class MainActivity extends CompatListActivity { private static final String EXTRA_PATH = "activity_path"; private static final String CATEGORY_HWUI_TEST = "com.android.test.uibench.TEST"; + private String mActivityPath = ""; + public static class TestListFragment extends ListFragment { @Override @SuppressWarnings("unchecked") @@ -56,9 +60,7 @@ public class MainActivity extends AppCompatActivity { } @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - + protected void initializeActivity() { Intent intent = getIntent(); String path = intent.getStringExtra(EXTRA_PATH); @@ -68,15 +70,19 @@ public class MainActivity extends AppCompatActivity { // not root level, display where we are in the hierarchy setTitle(path); } + mActivityPath = path; + } - FragmentManager fm = getSupportFragmentManager(); - if (fm.findFragmentById(android.R.id.content) == null) { - ListFragment listFragment = new TestListFragment(); - listFragment.setListAdapter(new SimpleAdapter(this, getData(path), - android.R.layout.simple_list_item_1, new String[] { "title" }, - new int[] { android.R.id.text1 })); - fm.beginTransaction().add(android.R.id.content, listFragment).commit(); - } + @Override + protected ListAdapter createListAdapter() { + return new SimpleAdapter(this, getData(mActivityPath), + android.R.layout.simple_list_item_1, new String[] { "title" }, + new int[] { android.R.id.text1 }); + } + + @Override + protected ListFragment createListFragment() { + return new TestListFragment(); } protected List<Map<String, Object>> getData(String prefix) { diff --git a/tests/UiBench/src/com/android/test/uibench/ShadowGridActivity.java b/tests/UiBench/src/com/android/test/uibench/ShadowGridActivity.java index af7c65acafd4..d6e1d06ca561 100644 --- a/tests/UiBench/src/com/android/test/uibench/ShadowGridActivity.java +++ b/tests/UiBench/src/com/android/test/uibench/ShadowGridActivity.java @@ -16,13 +16,15 @@ package com.android.test.uibench; import android.os.Bundle; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.ListFragment; -import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.widget.ArrayAdapter; +import android.widget.ListAdapter; + +import androidx.fragment.app.ListFragment; -public class ShadowGridActivity extends AppCompatActivity { +import com.android.test.uibench.listview.CompatListActivity; + +public class ShadowGridActivity extends CompatListActivity { public static class NoDividerListFragment extends ListFragment { @Override public void onViewCreated(View view, Bundle savedInstanceState) { @@ -31,18 +33,14 @@ public class ShadowGridActivity extends AppCompatActivity { } }; - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - FragmentManager fm = getSupportFragmentManager(); - if (fm.findFragmentById(android.R.id.content) == null) { - ListFragment listFragment = new NoDividerListFragment(); + protected ListAdapter createListAdapter() { + return new ArrayAdapter<>(this, R.layout.card_row, R.id.card_text, + TextUtils.buildSimpleStringList()); + } - listFragment.setListAdapter(new ArrayAdapter<>(this, - R.layout.card_row, R.id.card_text, TextUtils.buildSimpleStringList())); - fm.beginTransaction().add(android.R.id.content, listFragment).commit(); - } + @Override + protected ListFragment createListFragment() { + return new NoDividerListFragment(); } } diff --git a/tests/UiBench/src/com/android/test/uibench/WindowInsetsControllerActivity.java b/tests/UiBench/src/com/android/test/uibench/WindowInsetsControllerActivity.java new file mode 100644 index 000000000000..e4b89cdd5c8d --- /dev/null +++ b/tests/UiBench/src/com/android/test/uibench/WindowInsetsControllerActivity.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.uibench; + +import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP; + +import android.os.Bundle; +import android.os.PersistableBundle; +import android.view.WindowInsets; +import android.view.WindowInsetsAnimation; +import android.widget.EditText; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import java.util.List; + +public class WindowInsetsControllerActivity extends AppCompatActivity { + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EditText text = new EditText(this); + text.setText("WindowInsetsController"); + setContentView(text); + getWindow().setDecorFitsSystemWindows(false); + + text.setWindowInsetsAnimationCallback( + new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) { + @NonNull + @Override + public WindowInsets onProgress(@NonNull WindowInsets insets, + @NonNull List<WindowInsetsAnimation> runningAnimations) { + return WindowInsets.CONSUMED; + } + }); + } +} diff --git a/tests/UiBench/src/com/android/test/uibench/listview/CompatListActivity.java b/tests/UiBench/src/com/android/test/uibench/listview/CompatListActivity.java index 66595585c525..9a4b5270d5a7 100644 --- a/tests/UiBench/src/com/android/test/uibench/listview/CompatListActivity.java +++ b/tests/UiBench/src/com/android/test/uibench/listview/CompatListActivity.java @@ -16,22 +16,29 @@ package com.android.test.uibench.listview; import android.os.Bundle; +import android.widget.ListAdapter; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.ListFragment; -import androidx.appcompat.app.AppCompatActivity; -import android.widget.ListAdapter; public abstract class CompatListActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + initializeActivity(); + int containerViewId = getListFragmentContainerViewId(); FragmentManager fm = getSupportFragmentManager(); - if (fm.findFragmentById(android.R.id.content) == null) { + Fragment fragment = fm.findFragmentById(containerViewId); + if (fragment == null) { ListFragment listFragment = createListFragment(); listFragment.setListAdapter(createListAdapter()); - fm.beginTransaction().add(android.R.id.content, listFragment).commit(); + fm.beginTransaction().add(containerViewId, listFragment).commit(); + } else if (fragment instanceof ListFragment) { + ((ListFragment) fragment).setListAdapter(createListAdapter()); } } @@ -40,4 +47,11 @@ public abstract class CompatListActivity extends AppCompatActivity { protected ListFragment createListFragment() { return new ListFragment(); } + + protected int getListFragmentContainerViewId() { + return android.R.id.content; + } + + protected void initializeActivity() { + } } diff --git a/tests/UiBench/src/com/android/test/uibench/recyclerview/RvCompatListActivity.java b/tests/UiBench/src/com/android/test/uibench/recyclerview/RvCompatListActivity.java index bd313ad7636d..2f0d6ab901e2 100644 --- a/tests/UiBench/src/com/android/test/uibench/recyclerview/RvCompatListActivity.java +++ b/tests/UiBench/src/com/android/test/uibench/recyclerview/RvCompatListActivity.java @@ -17,15 +17,16 @@ package com.android.test.uibench.recyclerview; import android.content.Context; import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; -import androidx.appcompat.app.AppCompatActivity; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; import com.android.test.uibench.R; @@ -51,14 +52,21 @@ public abstract class RvCompatListActivity extends AppCompatActivity { super.onCreate(savedInstanceState); FragmentManager fm = getSupportFragmentManager(); - if (fm.findFragmentById(android.R.id.content) == null) { + Fragment existingFragment = fm.findFragmentById(android.R.id.content); + if (existingFragment == null) { RecyclerViewFragment fragment = new RecyclerViewFragment(); - fragment.layoutManager = createLayoutManager(this); - fragment.adapter = createAdapter(); + initializeRecyclerViewFragment(fragment); fm.beginTransaction().add(android.R.id.content, fragment).commit(); + } else if (existingFragment instanceof RecyclerViewFragment) { + initializeRecyclerViewFragment((RecyclerViewFragment) existingFragment); } } + private void initializeRecyclerViewFragment(RecyclerViewFragment fragment) { + fragment.layoutManager = createLayoutManager(this); + fragment.adapter = createAdapter(); + } + protected RecyclerView.LayoutManager createLayoutManager(Context context) { return new LinearLayoutManager(context); } diff --git a/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java b/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java index 7d9d0d52b002..7e8a13470c35 100644 --- a/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java +++ b/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java @@ -30,6 +30,7 @@ import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.usage.IntervalStats; +import com.android.server.usage.PackagesTokenData; import com.android.server.usage.UsageStatsDatabase; import com.android.server.usage.UsageStatsDatabase.StatCombiner; @@ -79,6 +80,7 @@ public class UsageStatsDatabasePerfTest { sContext = InstrumentationRegistry.getTargetContext(); mTestDir = new File(sContext.getFilesDir(), "UsageStatsDatabasePerfTest"); sUsageStatsDatabase = new UsageStatsDatabase(mTestDir); + sUsageStatsDatabase.readMappingsLocked(); sUsageStatsDatabase.init(1); } @@ -140,6 +142,37 @@ public class UsageStatsDatabasePerfTest { } } + private void runObfuscateStatsTest(int packageCount, int eventsPerPackage) { + final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState(); + IntervalStats intervalStats = new IntervalStats(); + populateIntervalStats(intervalStats, packageCount, eventsPerPackage); + long elapsedTimeNs = 0; + while (benchmarkState.keepRunning(elapsedTimeNs)) { + final long startTime = SystemClock.elapsedRealtimeNanos(); + PackagesTokenData packagesTokenData = new PackagesTokenData(); + intervalStats.obfuscateData(packagesTokenData); + final long endTime = SystemClock.elapsedRealtimeNanos(); + elapsedTimeNs = endTime - startTime; + clearUsageStatsFiles(); + } + } + + private void runDeobfuscateStatsTest(int packageCount, int eventsPerPackage) { + final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState(); + IntervalStats intervalStats = new IntervalStats(); + populateIntervalStats(intervalStats, packageCount, eventsPerPackage); + long elapsedTimeNs = 0; + while (benchmarkState.keepRunning(elapsedTimeNs)) { + PackagesTokenData packagesTokenData = new PackagesTokenData(); + intervalStats.obfuscateData(packagesTokenData); + final long startTime = SystemClock.elapsedRealtimeNanos(); + intervalStats.deobfuscateData(packagesTokenData); + final long endTime = SystemClock.elapsedRealtimeNanos(); + elapsedTimeNs = endTime - startTime; + clearUsageStatsFiles(); + } + } + @Test public void testQueryUsageStats_FewPkgsLightUse() throws IOException { runQueryUsageStatsTest(FEW_PKGS, LIGHT_USE); @@ -151,6 +184,16 @@ public class UsageStatsDatabasePerfTest { } @Test + public void testObfuscateStats_FewPkgsLightUse() { + runObfuscateStatsTest(FEW_PKGS, LIGHT_USE); + } + + @Test + public void testDeobfuscateStats_FewPkgsLightUse() { + runDeobfuscateStatsTest(FEW_PKGS, LIGHT_USE); + } + + @Test public void testQueryUsageStats_FewPkgsHeavyUse() throws IOException { runQueryUsageStatsTest(FEW_PKGS, HEAVY_USE); } @@ -161,6 +204,16 @@ public class UsageStatsDatabasePerfTest { } @Test + public void testObfuscateStats_FewPkgsHeavyUse() { + runObfuscateStatsTest(FEW_PKGS, HEAVY_USE); + } + + @Test + public void testDeobfuscateStats_FewPkgsHeavyUse() { + runDeobfuscateStatsTest(FEW_PKGS, HEAVY_USE); + } + + @Test public void testQueryUsageStats_ManyPkgsLightUse() throws IOException { runQueryUsageStatsTest(MANY_PKGS, LIGHT_USE); } @@ -171,6 +224,16 @@ public class UsageStatsDatabasePerfTest { } @Test + public void testObfuscateStats_ManyPkgsLightUse() { + runObfuscateStatsTest(MANY_PKGS, LIGHT_USE); + } + + @Test + public void testDeobfuscateStats_ManyPkgsLightUse() { + runDeobfuscateStatsTest(MANY_PKGS, LIGHT_USE); + } + + @Test public void testQueryUsageStats_ManyPkgsHeavyUse() throws IOException { runQueryUsageStatsTest(MANY_PKGS, HEAVY_USE); } @@ -179,4 +242,14 @@ public class UsageStatsDatabasePerfTest { public void testPutUsageStats_ManyPkgsHeavyUse() throws IOException { runPutUsageStatsTest(MANY_PKGS, HEAVY_USE); } + + @Test + public void testObfuscateStats_ManyPkgsHeavyUse() { + runObfuscateStatsTest(MANY_PKGS, HEAVY_USE); + } + + @Test + public void testDeobfuscateStats_ManyPkgsHeavyUse() { + runDeobfuscateStatsTest(MANY_PKGS, HEAVY_USE); + } } diff --git a/tests/FlickerTests/lib/test/Android.bp b/tests/UsbManagerTests/Android.bp index bfeb75b23469..a03c6e223b74 100644 --- a/tests/FlickerTests/lib/test/Android.bp +++ b/tests/UsbManagerTests/Android.bp @@ -1,5 +1,5 @@ // -// Copyright (C) 2018 The Android Open Source Project +// Copyright (C) 2019 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,19 +15,18 @@ // android_test { - name: "FlickerLibTest", - // sign this with platform cert, so this test is allowed to call private platform apis - certificate: "platform", - platform_apis: true, - test_suites: ["tests"], + name: "UsbManagerTests", srcs: ["src/**/*.java"], - libs: ["android.test.runner"], static_libs: [ + "frameworks-base-testutils", "androidx.test.rules", + "mockito-target-inline-minus-junit4", "platform-test-annotations", "truth-prebuilt", - "platformprotosnano", - "layersprotosnano", - "flickerlib", + "UsbManagerTestLib", ], + jni_libs: ["libdexmakerjvmtiagent"], + certificate: "platform", + platform_apis: true, + test_suites: ["device-tests"], } diff --git a/tests/UsbManagerTests/AndroidManifest.xml b/tests/UsbManagerTests/AndroidManifest.xml new file mode 100644 index 000000000000..4e0b790f6dde --- /dev/null +++ b/tests/UsbManagerTests/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.usbtest" > + + <uses-permission android:name="android.permission.MANAGE_USB" /> + + <application android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.usbtest" + android:label="UsbManagerTests"/> +</manifest> diff --git a/tests/UsbManagerTests/AndroidTest.xml b/tests/UsbManagerTests/AndroidTest.xml new file mode 100644 index 000000000000..c6e22cdc4b37 --- /dev/null +++ b/tests/UsbManagerTests/AndroidTest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs Frameworks USB API instrumentation Tests."> + <target_preparer class="com.android.tradefed.targetprep.TestFilePushSetup"/> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="UsbManagerTests.apk" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"/> + <option name="test-suite-tag" value="apct"/> + <option name="test-tag" value="UsbManagerTests" /> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.server.usbtest"/> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/tests/UsbManagerTests/lib/Android.bp b/tests/UsbManagerTests/lib/Android.bp new file mode 100644 index 000000000000..3c5d91b326d0 --- /dev/null +++ b/tests/UsbManagerTests/lib/Android.bp @@ -0,0 +1,34 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +android_library { + name: "UsbManagerTestLib", + srcs: ["src/**/*.java"], + static_libs: [ + "frameworks-base-testutils", + "androidx.test.rules", + "mockito-target-inline-minus-junit4", + "platform-test-annotations", + "services.core", + "services.net", + "services.usb", + "truth-prebuilt", + "androidx.core_core", + ], + libs: [ + "android.test.mock", + ], +} diff --git a/tests/RollbackTest/TestApp/res_v1/values-anydpi/values.xml b/tests/UsbManagerTests/lib/AndroidManifest.xml index 90d3da2565cc..c8b301ca0298 100644 --- a/tests/RollbackTest/TestApp/res_v1/values-anydpi/values.xml +++ b/tests/UsbManagerTests/lib/AndroidManifest.xml @@ -14,6 +14,9 @@ limitations under the License. --> -<resources> - <integer name="split_version">1</integer> -</resources> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.usblib"> + + <application/> + +</manifest> diff --git a/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java new file mode 100644 index 000000000000..782439f80fc8 --- /dev/null +++ b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.usblib; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.usb.UsbManager; +import android.os.RemoteException; +import android.util.Log; + +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests lib for {@link android.hardware.usb.UsbManager}. + */ +public class UsbManagerTestLib { + private static final String TAG = UsbManagerTestLib.class.getSimpleName(); + + private Context mContext; + + private UsbManager mUsbManagerSys; + private UsbManager mUsbManagerMock; + @Mock private android.hardware.usb.IUsbManager mMockUsbService; + + public UsbManagerTestLib(Context context) { + MockitoAnnotations.initMocks(this); + mContext = context; + + assertNotNull(mUsbManagerSys = mContext.getSystemService(UsbManager.class)); + assertNotNull(mUsbManagerMock = new UsbManager(mContext, mMockUsbService)); + } + + private long getCurrentFunctions() { + return mUsbManagerMock.getCurrentFunctions(); + } + + private void setCurrentFunctions(long functions) { + mUsbManagerMock.setCurrentFunctions(functions); + } + + private long getCurrentFunctionsSys() { + return mUsbManagerSys.getCurrentFunctions(); + } + + private void setCurrentFunctionsSys(long functions) { + mUsbManagerSys.setCurrentFunctions(functions); + } + + private void testSetGetCurrentFunctions_Matched(long functions) { + setCurrentFunctions(functions); + assertEquals("CurrentFunctions mismatched: ", functions, getCurrentFunctions()); + } + + private void testGetCurrentFunctionsMock_Matched(long functions) { + try { + when(mMockUsbService.getCurrentFunctions()).thenReturn(functions); + + assertEquals("CurrentFunctions mismatched: ", functions, getCurrentFunctions()); + } catch (RemoteException remEx) { + Log.w(TAG, "RemoteException"); + } + } + + private void testSetCurrentFunctionsMock_Matched(long functions) { + try { + setCurrentFunctions(functions); + + verify(mMockUsbService).setCurrentFunctions(eq(functions)); + } catch (RemoteException remEx) { + Log.w(TAG, "RemoteException"); + } + } + + public void testGetCurrentFunctionsSysEx() throws Exception { + getCurrentFunctionsSys(); + } + + public void testSetCurrentFunctionsSysEx(long functions) throws Exception { + setCurrentFunctionsSys(functions); + } + + public void testGetCurrentFunctionsEx() throws Exception { + getCurrentFunctions(); + + verify(mMockUsbService).getCurrentFunctions(); + } + + public void testSetCurrentFunctionsEx(long functions) throws Exception { + setCurrentFunctions(functions); + + verify(mMockUsbService).setCurrentFunctions(eq(functions)); + } + + public void testGetCurrentFunctions_shouldMatched() { + testGetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_NONE); + testGetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_MTP); + testGetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_PTP); + testGetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_MIDI); + testGetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_RNDIS); + } + + public void testSetCurrentFunctions_shouldMatched() { + testSetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_NONE); + testSetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_MTP); + testSetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_PTP); + testSetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_MIDI); + testSetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_RNDIS); + } +} diff --git a/tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java b/tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java new file mode 100644 index 000000000000..8b21763b4a24 --- /dev/null +++ b/tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.usbtest; + +import android.content.Context; +import android.hardware.usb.UsbManager; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; + +import com.android.server.usblib.UsbManagerTestLib; + +/** + * Unit tests for {@link android.hardware.usb.UsbManager}. + * Note: MUST claimed MANAGE_USB permission in Manifest + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class UsbManagerApiTest { + private Context mContext; + + private final UsbManagerTestLib mUsbManagerTestLib = + new UsbManagerTestLib(mContext = InstrumentationRegistry.getContext()); + + /** + * Verify NO SecurityException + * Go through System Server + */ + @Test + public void testUsbApi_GetCurrentFunctionsSys_shouldNoSecurityException() throws Exception { + mUsbManagerTestLib.testGetCurrentFunctionsSysEx(); + } + + /** + * Verify NO SecurityException + * Go through System Server + */ + @Test + public void testUsbApi_SetCurrentFunctionsSys_shouldNoSecurityException() throws Exception { + mUsbManagerTestLib.testSetCurrentFunctionsSysEx(UsbManager.FUNCTION_NONE); + } + + /** + * Verify NO SecurityException + * Go through Direct API, will not be denied by @RequiresPermission annotation + */ + @Test + public void testUsbApi_GetCurrentFunctions_shouldNoSecurityException() throws Exception { + mUsbManagerTestLib.testGetCurrentFunctionsEx(); + } + + /** + * Verify NO SecurityException + * Go through Direct API, will not be denied by @RequiresPermission annotation + */ + @Test + public void testUsbApi_SetCurrentFunctions_shouldNoSecurityException() throws Exception { + mUsbManagerTestLib.testSetCurrentFunctionsEx(UsbManager.FUNCTION_NONE); + } + + /** + * Verify API path from UsbManager to UsbService + */ + @Test + public void testUsbApi_GetCurrentFunctions_shouldMatched() { + mUsbManagerTestLib.testGetCurrentFunctions_shouldMatched(); + } + + /** + * Verify API path from UsbManager to UsbService + */ + @Test + public void testUsbApi_SetCurrentFunctions_shouldMatched() { + mUsbManagerTestLib.testSetCurrentFunctions_shouldMatched(); + } +} diff --git a/tests/UsbTests/Android.bp b/tests/UsbTests/Android.bp index 1b2cf638f514..7c2be9b63ac3 100644 --- a/tests/UsbTests/Android.bp +++ b/tests/UsbTests/Android.bp @@ -26,6 +26,7 @@ android_test { "services.net", "services.usb", "truth-prebuilt", + "UsbManagerTestLib", ], jni_libs: ["libdexmakerjvmtiagent"], certificate: "platform", diff --git a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java index ca1eb705e457..ef973acf763b 100644 --- a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java +++ b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java @@ -63,6 +63,8 @@ public class UsbHandlerTest { @Mock private UsbSettingsManager mUsbSettingsManager; @Mock + private UsbPermissionManager mUsbPermissionManager; + @Mock private SharedPreferences mSharedPreferences; @Mock private SharedPreferences.Editor mEditor; @@ -87,8 +89,9 @@ public class UsbHandlerTest { Intent mBroadcastedIntent; MockUsbHandler(Looper looper, Context context, UsbDeviceManager deviceManager, - UsbAlsaManager alsaManager, UsbSettingsManager settingsManager) { - super(looper, context, deviceManager, alsaManager, settingsManager); + UsbAlsaManager alsaManager, UsbSettingsManager settingsManager, + UsbPermissionManager permissionManager) { + super(looper, context, deviceManager, alsaManager, permissionManager); mUseUsbNotification = false; mIsUsbTransferAllowed = true; mCurrentUsbFunctionsReceived = true; @@ -142,7 +145,7 @@ public class UsbHandlerTest { mUsbHandler = new MockUsbHandler(FgThread.get().getLooper(), InstrumentationRegistry.getContext(), mUsbDeviceManager, mUsbAlsaManager, - mUsbSettingsManager); + mUsbSettingsManager, mUsbPermissionManager); } @SmallTest @@ -205,7 +208,7 @@ public class UsbHandlerTest { UsbManager.USB_FUNCTION_ADB); mUsbHandler = new MockUsbHandler(FgThread.get().getLooper(), InstrumentationRegistry.getContext(), mUsbDeviceManager, mUsbAlsaManager, - mUsbSettingsManager); + mUsbSettingsManager, mUsbPermissionManager); sendBootCompleteMessages(mUsbHandler); mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_ENABLE_ADB, 0)); @@ -228,7 +231,7 @@ public class UsbHandlerTest { mMockProperties.put(UsbDeviceManager.UsbHandler.USB_PERSISTENT_CONFIG_PROPERTY, "adb"); mUsbHandler = new MockUsbHandler(FgThread.get().getLooper(), InstrumentationRegistry.getContext(), mUsbDeviceManager, mUsbAlsaManager, - mUsbSettingsManager); + mUsbSettingsManager, mUsbPermissionManager); sendBootCompleteMessages(mUsbHandler); assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE); @@ -316,7 +319,7 @@ public class UsbHandlerTest { .thenReturn(UsbManager.USB_FUNCTION_MTP); mUsbHandler = new MockUsbHandler(FgThread.get().getLooper(), InstrumentationRegistry.getContext(), mUsbDeviceManager, mUsbAlsaManager, - mUsbSettingsManager); + mUsbSettingsManager, mUsbPermissionManager); sendBootCompleteMessages(mUsbHandler); mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_SCREEN_LOCK, 1)); mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_SCREEN_LOCK, 0)); diff --git a/tests/UsbTests/src/com/android/server/usb/UsbManagerNoPermTest.java b/tests/UsbTests/src/com/android/server/usb/UsbManagerNoPermTest.java new file mode 100644 index 000000000000..a0fd9d40506b --- /dev/null +++ b/tests/UsbTests/src/com/android/server/usb/UsbManagerNoPermTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.usb; + +import android.content.Context; +import android.hardware.usb.UsbManager; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; + +import com.android.server.usblib.UsbManagerTestLib; + +/** + * Unit tests for {@link android.hardware.usb.UsbManager}. + * Note: NOT claimed MANAGE_USB permission in Manifest + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class UsbManagerNoPermTest { + private Context mContext; + + private final UsbManagerTestLib mUsbManagerTestLib = + new UsbManagerTestLib(mContext = InstrumentationRegistry.getContext()); + + /** + * Verify SecurityException resulting from required permissions missing + * Go through System Server + */ + @Test(expected = SecurityException.class) + public void testUsbApi_GetCurrentFunctionsSys_OnSecurityException() throws Exception { + mUsbManagerTestLib.testGetCurrentFunctionsSysEx(); + } + + /** + * Verify SecurityException resulting from required permissions missing + * Go through System Server + */ + @Test(expected = SecurityException.class) + public void testUsbApi_SetCurrentFunctionsSys_OnSecurityException() throws Exception { + mUsbManagerTestLib.testSetCurrentFunctionsSysEx(UsbManager.FUNCTION_NONE); + } + + /** + * Verify SecurityException resulting from required permissions missing + * Go through Direct API, will not be denied by @RequiresPermission annotation + */ + @Test(expected = SecurityException.class) + @Ignore + public void testUsbApi_GetCurrentFunctions_OnSecurityException() throws Exception { + mUsbManagerTestLib.testGetCurrentFunctionsEx(); + } + + /** + * Verify SecurityException resulting from required permissions missing + * Go through Direct API, will not be denied by @RequiresPermission annotation + */ + @Test(expected = SecurityException.class) + @Ignore + public void testUsbApi_SetCurrentFunctions_OnSecurityException() throws Exception { + mUsbManagerTestLib.testSetCurrentFunctionsEx(UsbManager.FUNCTION_NONE); + } +} diff --git a/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/EnrollmentUtil.java b/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/EnrollmentUtil.java index 7927ac4cb895..287364fec8eb 100644 --- a/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/EnrollmentUtil.java +++ b/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/EnrollmentUtil.java @@ -159,36 +159,36 @@ public class EnrollmentUtil { Log.e(TAG, "KeyphraseSoundModel must be non-null"); return false; } - if (soundModel.uuid == null) { + if (soundModel.getUuid() == null) { Log.e(TAG, "KeyphraseSoundModel must have a UUID"); return false; } - if (soundModel.data == null) { + if (soundModel.getData() == null) { Log.e(TAG, "KeyphraseSoundModel must have data"); return false; } - if (soundModel.keyphrases == null || soundModel.keyphrases.length != 1) { + if (soundModel.getKeyphrases() == null || soundModel.getKeyphrases().length != 1) { Log.e(TAG, "Keyphrase must be exactly 1"); return false; } - Keyphrase keyphrase = soundModel.keyphrases[0]; - if (keyphrase.id <= 0) { + Keyphrase keyphrase = soundModel.getKeyphrases()[0]; + if (keyphrase.getId() <= 0) { Log.e(TAG, "Keyphrase must have a valid ID"); return false; } - if (keyphrase.recognitionModes < 0) { + if (keyphrase.getRecognitionModes() < 0) { Log.e(TAG, "Recognition modes must be valid"); return false; } - if (keyphrase.locale == null) { + if (keyphrase.getLocale() == null) { Log.e(TAG, "Locale must not be null"); return false; } - if (keyphrase.text == null) { + if (keyphrase.getText() == null) { Log.e(TAG, "Text must not be null"); return false; } - if (keyphrase.users == null || keyphrase.users.length == 0) { + if (keyphrase.getUsers() == null || keyphrase.getUsers().length == 0) { Log.e(TAG, "Keyphrase must have valid user(s)"); return false; } diff --git a/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java b/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java index 54c944f9588e..e4880fd10d67 100644 --- a/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java +++ b/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java @@ -16,9 +16,6 @@ package com.android.test.voiceenrollment; -import java.util.Random; -import java.util.UUID; - import android.app.Activity; import android.hardware.soundtrigger.SoundTrigger; import android.hardware.soundtrigger.SoundTrigger.Keyphrase; @@ -29,6 +26,13 @@ import android.util.Log; import android.view.View; import android.widget.Toast; +import java.util.Locale; +import java.util.Random; +import java.util.UUID; + +/** + * TODO: must be transitioned to a service. + */ public class TestEnrollmentActivity extends Activity { private static final String TAG = "TestEnrollmentActivity"; private static final boolean DBG = false; @@ -56,7 +60,8 @@ public class TestEnrollmentActivity extends Activity { * Performs a fresh enrollment. */ public void onEnrollButtonClicked(View v) { - Keyphrase kp = new Keyphrase(KEYPHRASE_ID, RECOGNITION_MODES, BCP47_LOCALE, TEXT, + Keyphrase kp = new Keyphrase(KEYPHRASE_ID, RECOGNITION_MODES, + Locale.forLanguageTag(BCP47_LOCALE), TEXT, new int[] { UserManager.get(this).getUserHandle() /* current user */}); UUID modelUuid = UUID.randomUUID(); // Generate a fake model to push. @@ -86,7 +91,7 @@ public class TestEnrollmentActivity extends Activity { } boolean status = mEnrollmentUtil.deleteSoundModel(KEYPHRASE_ID, BCP47_LOCALE); if (status) { - Toast.makeText(this, "Successfully un-enrolled, model UUID=" + soundModel.uuid, + Toast.makeText(this, "Successfully un-enrolled, model UUID=" + soundModel.getUuid(), Toast.LENGTH_SHORT) .show(); } else { @@ -107,11 +112,11 @@ public class TestEnrollmentActivity extends Activity { // Generate a fake model to push. byte[] data = new byte[2048]; mRandom.nextBytes(data); - KeyphraseSoundModel updated = new KeyphraseSoundModel(soundModel.uuid, - soundModel.vendorUuid, data, soundModel.keyphrases); + KeyphraseSoundModel updated = new KeyphraseSoundModel(soundModel.getUuid(), + soundModel.getVendorUuid(), data, soundModel.getKeyphrases()); boolean status = mEnrollmentUtil.addOrUpdateSoundModel(updated); if (status) { - Toast.makeText(this, "Successfully re-enrolled, model UUID=" + updated.uuid, + Toast.makeText(this, "Successfully re-enrolled, model UUID=" + updated.getUuid(), Toast.LENGTH_SHORT) .show(); } else { diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java index f1dd1de0e517..b7f5b15f72ac 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java @@ -16,14 +16,11 @@ package com.android.test.voiceinteraction; -import android.content.ComponentName; import android.content.Intent; -import android.os.Bundle; import android.service.voice.AlwaysOnHotwordDetector; import android.service.voice.AlwaysOnHotwordDetector.Callback; import android.service.voice.AlwaysOnHotwordDetector.EventPayload; import android.service.voice.VoiceInteractionService; -import android.service.voice.VoiceInteractionSession; import android.util.Log; import java.util.Arrays; @@ -68,7 +65,8 @@ public class MainInteractionService extends VoiceInteractionService { Log.i(TAG, "Creating " + this); Log.i(TAG, "Keyphrase enrollment error? " + getKeyphraseEnrollmentInfo().getParseError()); Log.i(TAG, "Keyphrase enrollment meta-data: " - + Arrays.toString(getKeyphraseEnrollmentInfo().listKeyphraseMetadata())); + + Arrays.toString(getKeyphraseEnrollmentInfo().listKeyphraseMetadata().toArray( + new android.hardware.soundtrigger.KeyphraseMetadata[0]))); mHotwordDetector = createAlwaysOnHotwordDetector( "Hello There", Locale.forLanguageTag("en-US"), mHotwordCallback); diff --git a/tests/WindowInsetsTests/Android.bp b/tests/WindowInsetsTests/Android.bp new file mode 100644 index 000000000000..7272152dc257 --- /dev/null +++ b/tests/WindowInsetsTests/Android.bp @@ -0,0 +1,27 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_test { + name: "WindowInsetsTests", + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + certificate: "platform", + platform_apis: true, + static_libs: [ + "androidx.core_core", + "androidx.appcompat_appcompat", + "com.google.android.material_material", + ], +} + diff --git a/tests/WindowManagerStressTest/AndroidManifest.xml b/tests/WindowInsetsTests/AndroidManifest.xml index 17e0f15c29a9..0f6282e20b41 100644 --- a/tests/WindowManagerStressTest/AndroidManifest.xml +++ b/tests/WindowInsetsTests/AndroidManifest.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2017 The Android Open Source Project + ~ Copyright (018C) 2018 The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. @@ -16,15 +16,13 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="test.windowmanagerstresstest"> + package="com.google.android.test.windowinsetstests"> + + <application android:label="@string/activity_title"> + <activity android:name=".WindowInsetsActivity" + android:theme="@style/appTheme" + android:windowSoftInputMode="adjustResize"> - <application - android:allowBackup="false" - android:icon="@mipmap/ic_launcher" - android:label="@string/app_name" - android:supportsRtl="true" - android:theme="@style/AppTheme"> - <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> diff --git a/tests/WindowInsetsTests/res/drawable/bubble.xml b/tests/WindowInsetsTests/res/drawable/bubble.xml new file mode 100644 index 000000000000..26deb1e59e41 --- /dev/null +++ b/tests/WindowInsetsTests/res/drawable/bubble.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="@color/bubble" /> + <corners android:radius="@dimen/bubble_corner" /> + <padding android:left="@dimen/bubble_padding_side" android:top="@dimen/bubble_padding" + android:right="@dimen/bubble_padding_side" android:bottom="@dimen/bubble_padding" /> +</shape>
\ No newline at end of file diff --git a/tests/WindowInsetsTests/res/drawable/bubble_self.xml b/tests/WindowInsetsTests/res/drawable/bubble_self.xml new file mode 100644 index 000000000000..5f098a2fea60 --- /dev/null +++ b/tests/WindowInsetsTests/res/drawable/bubble_self.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="@color/bubble_self" /> + <corners android:radius="@dimen/bubble_corner" /> + <padding android:left="@dimen/bubble_padding_side" android:top="@dimen/bubble_padding" + android:right="@dimen/bubble_padding_side" android:bottom="@dimen/bubble_padding" /> +</shape>
\ No newline at end of file diff --git a/tests/WindowInsetsTests/res/drawable/ic_send.xml b/tests/WindowInsetsTests/res/drawable/ic_send.xml new file mode 100644 index 000000000000..15bc411e85fb --- /dev/null +++ b/tests/WindowInsetsTests/res/drawable/ic_send.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:autoMirrored="true" + android:width="24.0dp" + android:height="24.0dp" + android:viewportWidth="48.0" + android:viewportHeight="48.0"> + <path + android:fillColor="#FF000000" + android:pathData="M4.02,42.0L46.0,24.0 4.02,6.0 4.0,20.0l30.0,4.0 -30.0,4.0z"/> +</vector> diff --git a/tests/WindowInsetsTests/res/layout/message.xml b/tests/WindowInsetsTests/res/layout/message.xml new file mode 100644 index 000000000000..d6b29c33f662 --- /dev/null +++ b/tests/WindowInsetsTests/res/layout/message.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<TextView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@drawable/bubble" + android:textSize="32sp" + android:text="Hello World"> + +</TextView>
\ No newline at end of file diff --git a/tests/WindowInsetsTests/res/layout/message_self.xml b/tests/WindowInsetsTests/res/layout/message_self.xml new file mode 100644 index 000000000000..de34e4896331 --- /dev/null +++ b/tests/WindowInsetsTests/res/layout/message_self.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<merge> + <include + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + layout="@layout/message"> + <aapt:attr name="android:theme"> + <style> + <item name="android:layout_gravity">end</item> + <item name="bubbleBackground">@color/bubble_self</item> + </style> + </aapt:attr> + </include> +</merge> diff --git a/tests/WindowInsetsTests/res/layout/window_inset_activity.xml b/tests/WindowInsetsTests/res/layout/window_inset_activity.xml new file mode 100644 index 000000000000..1b51c4f83fe0 --- /dev/null +++ b/tests/WindowInsetsTests/res/layout/window_inset_activity.xml @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:clipToPadding="false" + android:id="@+id/root"> + + <androidx.appcompat.widget.Toolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + /> + + <FrameLayout + android:id="@+id/scrollView" + android:layout_height="0dp" + android:layout_width="match_parent" + android:paddingStart="8dp" + android:paddingEnd="8dp" + android:layout_weight="1"> + + <LinearLayout + android:orientation="vertical" + android:layout_gravity="bottom" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@style/bubble" + android:text="Hey, look at this buttery smooth animation!" /> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@style/bubble_self" + android:text="Wow, that's pretty neat, how does this work?" /> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@style/bubble" + android:text="Using the new WindowInsets animation system of course!" /> + + </LinearLayout> + + </FrameLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingStart="8dp" + android:paddingEnd="8dp" + android:id="@+id/editText"> + + <com.google.android.material.textfield.TextInputLayout + style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1"> + + <com.google.android.material.textfield.TextInputEditText + android:hint="Text message" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + + </com.google.android.material.textfield.TextInputLayout> + + <com.google.android.material.floatingactionbutton.FloatingActionButton + android:id="@+id/floating_action_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + app:elevation="0dp" + app:fabSize="mini" + app:srcCompat="@drawable/ic_send"/> + + </LinearLayout> +</LinearLayout> + diff --git a/tests/WindowManagerStressTest/res/values/strings.xml b/tests/WindowInsetsTests/res/values/strings.xml index cef05dcb6584..2b8e5f3da362 100644 --- a/tests/WindowManagerStressTest/res/values/strings.xml +++ b/tests/WindowInsetsTests/res/values/strings.xml @@ -1,5 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2017 The Android Open Source Project + ~ Copyright (C) 2018 The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. @@ -13,7 +14,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> + <resources> - <string name="app_name">WmSlam</string> - <string name="run">Run</string> + <string name="activity_title">New Insets Chat</string> </resources> diff --git a/tests/WindowInsetsTests/res/values/styles.xml b/tests/WindowInsetsTests/res/values/styles.xml new file mode 100644 index 000000000000..220671fb8e71 --- /dev/null +++ b/tests/WindowInsetsTests/res/values/styles.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + + <style name="appTheme" parent="@style/Theme.MaterialComponents.Light"> + <item name="windowActionBar">false</item> + <item name="windowNoTitle">true</item> + + <item name="colorPrimary">@color/primaryColor</item> + <item name="colorPrimaryDark">@color/primaryDarkColor</item> + <item name="colorSecondary">?attr/colorPrimary</item> + <item name="colorOnSecondary">@color/primaryTextColor</item> + + <!-- Window decor --> + <item name="android:statusBarColor">#ffffff</item> + <item name="android:windowLightStatusBar">true</item> + <item name="android:windowLightNavigationBar">true</item> + <item name="android:navigationBarColor">#ffffff</item> + + </style> + + <style name="bubble_base" parent=""> + <item name="android:textSize">20sp</item> + <item name="android:layout_marginBottom">16dp</item> + </style> + + <style name="bubble" parent="@style/bubble_base"> + <item name="android:layout_marginEnd">56dp</item> + <item name="android:background">@drawable/bubble</item> + <item name="android:layout_gravity">start</item> + </style> + + <style name="bubble_self" parent="@style/bubble_base"> + <item name="android:layout_marginStart">56dp</item> + <item name="android:background">@drawable/bubble_self</item> + <item name="android:layout_gravity">end</item> + </style> + + <color name="primaryColor">#1c3fef</color> + <color name="primaryLightColor">#6f6bff</color> + <color name="primaryDarkColor">#0016bb</color> + <color name="primaryTextColor">#ffffff</color> + + <color name="bubble">#eeeeee</color> + <color name="bubble_self">#D8DCF0</color> + + <dimen name="bubble_corner">16dp</dimen> + <dimen name="bubble_padding">8dp</dimen> + <dimen name="bubble_padding_side">16dp</dimen> + + +</resources>
\ No newline at end of file diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java new file mode 100644 index 000000000000..498cb7c1c710 --- /dev/null +++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.google.android.test.windowinsetstests; + +import static android.view.WindowInsets.Type.ime; +import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP; +import static java.lang.Math.max; +import static java.lang.Math.min; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.Insets; +import android.os.Bundle; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.WindowInsets; +import android.view.WindowInsetsAnimation; +import android.view.WindowInsetsAnimation.Callback; +import android.view.WindowInsetsAnimationControlListener; +import android.view.WindowInsetsAnimationController; +import android.view.WindowInsetsController; +import android.view.WindowInsetsController.OnControllableInsetsChangedListener; +import android.view.animation.LinearInterpolator; +import android.widget.LinearLayout; + +import java.util.ArrayList; +import java.util.List; + +import androidx.appcompat.app.AppCompatActivity; + +public class WindowInsetsActivity extends AppCompatActivity { + + private View mRoot; + + final ArrayList<Transition> mTransitions = new ArrayList<>(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.window_inset_activity); + + setSupportActionBar(findViewById(R.id.toolbar)); + + mRoot = findViewById(R.id.root); + mRoot.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + + mTransitions.add(new Transition(findViewById(R.id.scrollView))); + mTransitions.add(new Transition(findViewById(R.id.editText))); + + mRoot.setOnTouchListener(new View.OnTouchListener() { + private final ViewConfiguration mViewConfiguration = + ViewConfiguration.get(WindowInsetsActivity.this); + WindowInsetsAnimationController mAnimationController; + WindowInsetsAnimationControlListener mCurrentRequest; + boolean mRequestedController = false; + float mDown = 0; + float mCurrent = 0; + Insets mDownInsets = Insets.NONE; + boolean mShownAtDown; + + @Override + public boolean onTouch(View v, MotionEvent event) { + mCurrent = event.getY(); + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mDown = event.getY(); + mDownInsets = v.getRootWindowInsets().getInsets(ime()); + mShownAtDown = v.getRootWindowInsets().isVisible(ime()); + mRequestedController = false; + mCurrentRequest = null; + break; + case MotionEvent.ACTION_MOVE: + if (mAnimationController != null) { + updateInset(); + } else if (Math.abs(mDown - event.getY()) + > mViewConfiguration.getScaledTouchSlop() + && !mRequestedController) { + mRequestedController = true; + v.getWindowInsetsController().controlWindowInsetsAnimation(ime(), + 1000, new LinearInterpolator(), null /* cancellationSignal */, + mCurrentRequest = new WindowInsetsAnimationControlListener() { + @Override + public void onReady( + @NonNull WindowInsetsAnimationController controller, + int types) { + if (mCurrentRequest == this) { + mAnimationController = controller; + updateInset(); + } else { + controller.finish(mShownAtDown); + } + } + + @Override + public void onFinished( + WindowInsetsAnimationController controller) { + mAnimationController = null; + } + + @Override + public void onCancelled( + WindowInsetsAnimationController controller) { + mAnimationController = null; + } + }); + } + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + if (mAnimationController != null) { + boolean isCancel = event.getAction() == MotionEvent.ACTION_CANCEL; + mAnimationController.finish(isCancel ? mShownAtDown : !mShownAtDown); + mAnimationController = null; + } + mRequestedController = false; + mCurrentRequest = null; + break; + } + return true; + } + + private void updateInset() { + int inset = (int) (mDownInsets.bottom + (mDown - mCurrent)); + final int hidden = mAnimationController.getHiddenStateInsets().bottom; + final int shown = mAnimationController.getShownStateInsets().bottom; + final int start = mShownAtDown ? shown : hidden; + final int end = mShownAtDown ? hidden : shown; + inset = max(inset, hidden); + inset = min(inset, shown); + mAnimationController.setInsetsAndAlpha( + Insets.of(0, 0, 0, inset), + 1f, (inset - start) / (float)(end - start)); + } + }); + + mRoot.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { + @Override + public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { + mRoot.setPadding(insets.getSystemWindowInsetLeft(), + insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), + insets.getSystemWindowInsetBottom()); + return WindowInsets.CONSUMED; + } + }); + + mRoot.setWindowInsetsAnimationCallback(new Callback(DISPATCH_MODE_STOP) { + + @Override + public void onPrepare(WindowInsetsAnimation animation) { + mTransitions.forEach(it -> it.onPrepare(animation)); + } + + @Override + public WindowInsets onProgress(WindowInsets insets, + @NonNull List<WindowInsetsAnimation> runningAnimations) { + mTransitions.forEach(it -> it.onProgress(insets)); + return insets; + } + + @Override + public WindowInsetsAnimation.Bounds onStart(WindowInsetsAnimation animation, + WindowInsetsAnimation.Bounds bounds) { + mTransitions.forEach(Transition::onStart); + return bounds; + } + + @Override + public void onEnd(WindowInsetsAnimation animation) { + mTransitions.forEach(it -> it.onFinish(animation)); + } + }); + + findViewById(R.id.floating_action_button).setOnClickListener( + v -> v.getWindowInsetsController().controlWindowInsetsAnimation(ime(), -1, + new LinearInterpolator(), null /* cancellationSignal */, + new WindowInsetsAnimationControlListener() { + @Override + public void onReady( + WindowInsetsAnimationController controller, + int types) { + ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); + anim.setDuration(1500); + anim.addUpdateListener(animation + -> controller.setInsetsAndAlpha( + controller.getShownStateInsets(), + (float) animation.getAnimatedValue(), + anim.getAnimatedFraction())); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + controller.finish(true); + } + }); + anim.start(); + } + + @Override + public void onCancelled(WindowInsetsAnimationController controller) { + } + + @Override + public void onFinished(WindowInsetsAnimationController controller) { + } + })); + } + + @Override + public void onResume() { + super.onResume(); + // TODO: move this to onCreate once setDecorFitsSystemWindows can be safely called there. + getWindow().getDecorView().post(() -> getWindow().setDecorFitsSystemWindows(false)); + } + + static class Transition { + private int mEndBottom; + private int mStartBottom; + private final View mView; + private WindowInsetsAnimation mInsetsAnimation; + + Transition(View root) { + mView = root; + } + + void onPrepare(WindowInsetsAnimation animation) { + if ((animation.getTypeMask() & ime()) != 0) { + mInsetsAnimation = animation; + } + mStartBottom = mView.getBottom(); + } + + void onProgress(WindowInsets insets) { + mView.setY(mStartBottom + (mEndBottom - mStartBottom) + * mInsetsAnimation.getInterpolatedFraction() + - mView.getHeight()); + } + + void onStart() { + mEndBottom = mView.getBottom(); + } + + void onFinish(WindowInsetsAnimation animation) { + if (mInsetsAnimation == animation) { + mInsetsAnimation = null; + } + } + } + + static class ImeLinearLayout extends LinearLayout { + + public ImeLinearLayout(Context context, + @Nullable AttributeSet attrs) { + super(context, attrs); + } + } +} diff --git a/tests/WindowManagerStressTest/res/layout/activity_main.xml b/tests/WindowManagerStressTest/res/layout/activity_main.xml deleted file mode 100644 index 6cf82691155c..000000000000 --- a/tests/WindowManagerStressTest/res/layout/activity_main.xml +++ /dev/null @@ -1,38 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:paddingBottom="@dimen/activity_vertical_margin" - android:paddingLeft="@dimen/activity_horizontal_margin" - android:paddingRight="@dimen/activity_horizontal_margin" - android:paddingTop="@dimen/activity_vertical_margin" - tools:context="test.amslam.MainActivity"> - - <Button - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:id="@+id/run" - android:text="@string/run" /> - - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:id="@+id/output" /> - -</LinearLayout> diff --git a/tests/WindowManagerStressTest/res/mipmap-hdpi/ic_launcher.png b/tests/WindowManagerStressTest/res/mipmap-hdpi/ic_launcher.png Binary files differdeleted file mode 100644 index cde69bcccec6..000000000000 --- a/tests/WindowManagerStressTest/res/mipmap-hdpi/ic_launcher.png +++ /dev/null diff --git a/tests/WindowManagerStressTest/res/mipmap-mdpi/ic_launcher.png b/tests/WindowManagerStressTest/res/mipmap-mdpi/ic_launcher.png Binary files differdeleted file mode 100644 index c133a0cbd379..000000000000 --- a/tests/WindowManagerStressTest/res/mipmap-mdpi/ic_launcher.png +++ /dev/null diff --git a/tests/WindowManagerStressTest/res/mipmap-xhdpi/ic_launcher.png b/tests/WindowManagerStressTest/res/mipmap-xhdpi/ic_launcher.png Binary files differdeleted file mode 100644 index bfa42f0e7b91..000000000000 --- a/tests/WindowManagerStressTest/res/mipmap-xhdpi/ic_launcher.png +++ /dev/null diff --git a/tests/WindowManagerStressTest/res/mipmap-xxhdpi/ic_launcher.png b/tests/WindowManagerStressTest/res/mipmap-xxhdpi/ic_launcher.png Binary files differdeleted file mode 100644 index 324e72cdd748..000000000000 --- a/tests/WindowManagerStressTest/res/mipmap-xxhdpi/ic_launcher.png +++ /dev/null diff --git a/tests/WindowManagerStressTest/res/mipmap-xxxhdpi/ic_launcher.png b/tests/WindowManagerStressTest/res/mipmap-xxxhdpi/ic_launcher.png Binary files differdeleted file mode 100644 index aee44e138434..000000000000 --- a/tests/WindowManagerStressTest/res/mipmap-xxxhdpi/ic_launcher.png +++ /dev/null diff --git a/tests/WindowManagerStressTest/res/values/colors.xml b/tests/WindowManagerStressTest/res/values/colors.xml deleted file mode 100644 index 4270ca68a860..000000000000 --- a/tests/WindowManagerStressTest/res/values/colors.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<resources> - <color name="colorPrimary">#3F51B5</color> - <color name="colorPrimaryDark">#303F9F</color> - <color name="colorAccent">#FF4081</color> -</resources> diff --git a/tests/WindowManagerStressTest/res/values/dimens.xml b/tests/WindowManagerStressTest/res/values/dimens.xml deleted file mode 100644 index ed4ccbcc700f..000000000000 --- a/tests/WindowManagerStressTest/res/values/dimens.xml +++ /dev/null @@ -1,19 +0,0 @@ -<!-- 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. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<resources> - <!-- Default screen margins, per the Android Design guidelines. --> - <dimen name="activity_horizontal_margin">16dp</dimen> - <dimen name="activity_vertical_margin">16dp</dimen> -</resources> diff --git a/tests/WindowManagerStressTest/res/values/styles.xml b/tests/WindowManagerStressTest/res/values/styles.xml deleted file mode 100644 index 0983b2535878..000000000000 --- a/tests/WindowManagerStressTest/res/values/styles.xml +++ /dev/null @@ -1,23 +0,0 @@ -<!-- 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. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<resources> - <!-- Base application theme. --> - <style name="AppTheme" parent="@android:style/Theme.Material.Light.DarkActionBar"> - <!-- Customize your theme here. --> - <item name="android:colorPrimary">@color/colorPrimary</item> - <item name="android:colorPrimaryDark">@color/colorPrimaryDark</item> - <item name="android:colorAccent">@color/colorAccent</item> - </style> -</resources> diff --git a/tests/WindowManagerStressTest/src/test/windowmanagerstresstest/MainActivity.java b/tests/WindowManagerStressTest/src/test/windowmanagerstresstest/MainActivity.java deleted file mode 100644 index 85646987940f..000000000000 --- a/tests/WindowManagerStressTest/src/test/windowmanagerstresstest/MainActivity.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package test.windowmanagerstresstest; - -import android.app.Activity; -import android.graphics.Rect; -import android.os.Bundle; -import android.os.RemoteException; -import android.os.SystemClock; -import android.util.Log; -import android.util.MergedConfiguration; -import android.view.Display; -import android.view.DisplayCutout; -import android.view.IWindowSession; -import android.view.InsetsState; -import android.view.Surface; -import android.view.SurfaceControl; -import android.view.View; -import android.view.WindowManager; -import android.view.WindowManager.LayoutParams; -import android.view.WindowManagerGlobal; -import android.widget.TextView; - -import com.android.internal.view.BaseIWindow; - -import java.util.ArrayList; - -public class MainActivity extends Activity { - - private static final String TAG = "WmSlam"; - - private TextView mOutput; - private volatile boolean finished; - private final ArrayList<BaseIWindow> mWindows = new ArrayList<>(); - private final LayoutParams mLayoutParams = new LayoutParams(); - private final Rect mTmpRect = new Rect(); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - mOutput = (TextView) findViewById(R.id.output); - - findViewById(R.id.run).setOnClickListener(view -> { - view.setEnabled(false); - mOutput.setText(""); - startBatch(); - }); - mLayoutParams.token = getActivityToken(); - } - - void startBatch() { - new Thread(() -> { - finished = false; - addWindows(); - startCpuRunnables(); - for (int i = 0; i < 5; i++) { - final long time = SystemClock.uptimeMillis(); - slamWm(); - log("Total: " + (SystemClock.uptimeMillis() - time) + " ms"); - } - removeWindows(); - finished = true; - }).start(); - } - - void startCpuRunnables() { - for (int i = 0; i < 10; i++) { - new Thread(mUseCpuRunnable).start(); - } - } - - private final Runnable mUseCpuRunnable = new Runnable() { - @Override - public void run() { - while (!finished) { - } - } - }; - - private void log(String text) { - mOutput.post(() -> mOutput.append(text + "\n")); - Log.d(TAG, text); - } - - private void slamWm() { - ArrayList<Thread> threads = new ArrayList<>(); - for (int i = 0; i < 20; i++) { - for (BaseIWindow window : mWindows) { - Thread t = new Thread(() -> { - try { - WindowManagerGlobal.getWindowSession().relayout(window, - window.mSeq, mLayoutParams, -1, -1, View.VISIBLE, 0, -1, mTmpRect, - mTmpRect, mTmpRect, mTmpRect, mTmpRect, mTmpRect, mTmpRect, - new DisplayCutout.ParcelableWrapper(), new MergedConfiguration(), - new SurfaceControl(), new InsetsState()); - } catch (RemoteException e) { - e.printStackTrace(); - } - }); - threads.add(t); - t.start(); - } - } - for (Thread t : threads) { - try { - t.join(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - - void addWindows() { - for (int i = 0; i < 50; i++) { - final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); - layoutParams.token = getActivityToken(); - final BaseIWindow window = new BaseIWindow(); - final IWindowSession session = WindowManagerGlobal.getWindowSession(); - final Rect tmpRect = new Rect(); - try { - final int res = session.addToDisplayWithoutInputChannel(window, window.mSeq, - layoutParams, View.VISIBLE, Display.DEFAULT_DISPLAY, tmpRect, tmpRect, - new InsetsState()); - } catch (RemoteException e) { - e.printStackTrace(); - } - mWindows.add(window); - } - } - - void removeWindows() { - for (BaseIWindow window : mWindows) { - try { - WindowManagerGlobal.getWindowSession().remove(window); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - } -} diff --git a/tests/libs-permissions/Android.bp b/tests/libs-permissions/Android.bp index c7c4b10a8405..66a1f83dc308 100644 --- a/tests/libs-permissions/Android.bp +++ b/tests/libs-permissions/Android.bp @@ -2,6 +2,7 @@ java_library { name: "com.android.test.libs.product", installable: true, product_specific: true, + sdk_version: "current", srcs: ["product/java/**/*.java"], required: ["com.android.test.libs.product.xml"], } @@ -14,16 +15,16 @@ prebuilt_etc { } java_library { - name: "com.android.test.libs.product_services", + name: "com.android.test.libs.system_ext", installable: true, - product_services_specific: true, - srcs: ["product_services/java/**/*.java"], - required: ["com.android.test.libs.product_services.xml"], + system_ext_specific: true, + srcs: ["system_ext/java/**/*.java"], + required: ["com.android.test.libs.system_ext.xml"], } prebuilt_etc { - name: "com.android.test.libs.product_services.xml", - src: "product_services/com.android.test.libs.product_services.xml", + name: "com.android.test.libs.system_ext.xml", + src: "system_ext/com.android.test.libs.system_ext.xml", sub_dir: "permissions", - product_services_specific: true, + system_ext_specific: true, } diff --git a/tests/libs-permissions/product_services/com.android.test.libs.product_services.xml b/tests/libs-permissions/system_ext/com.android.test.libs.system_ext.xml index 082a9be80779..fa56004415f9 100644 --- a/tests/libs-permissions/product_services/com.android.test.libs.product_services.xml +++ b/tests/libs-permissions/system_ext/com.android.test.libs.system_ext.xml @@ -15,6 +15,6 @@ --> <permissions> - <library name="com.android.test.libs.product_services" - file="/product_services/framework/com.android.test.libs.product_services.jar" /> + <library name="com.android.test.libs.system_ext" + file="/system_ext/framework/com.android.test.libs.system_ext.jar" /> </permissions> diff --git a/tests/libs-permissions/product_services/java/com/android/test/libs/product_services/LibsProductServicesTest.java b/tests/libs-permissions/system_ext/java/com/android/test/libs/system_ext/LibsSystemExtTest.java index dcbdae809889..9999aba37d8d 100644 --- a/tests/libs-permissions/product_services/java/com/android/test/libs/product_services/LibsProductServicesTest.java +++ b/tests/libs-permissions/system_ext/java/com/android/test/libs/system_ext/LibsSystemExtTest.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.test.libs.product_services; +package com.android.test.libs.system_ext; /** - * Test class for product_services libs. + * Test class for system_ext libs. */ -public class LibsProductServicesTest { +public class LibsSystemExtTest { /** * Dummy method for testing. diff --git a/tests/net/Android.bp b/tests/net/Android.bp index 306cc515c870..124b6609f687 100644 --- a/tests/net/Android.bp +++ b/tests/net/Android.bp @@ -3,69 +3,68 @@ //######################################################################## java_defaults { name: "FrameworksNetTests-jni-defaults", - static_libs: [ - "FrameworksNetCommonTests", - "frameworks-base-testutils", - "frameworks-net-testutils", - "framework-protos", - "androidx.test.rules", - "mockito-target-minus-junit4", - "platform-test-annotations", - "services.core", - "services.net", - ], - libs: [ - "android.test.runner", - "android.test.base", - "android.test.mock", - ], jni_libs: [ "ld-android", - "libartbase", "libbacktrace", "libbase", "libbinder", - "libbinderthreadstate", "libbpf", "libbpf_android", "libc++", "libcgrouprc", "libcrypto", "libcutils", - "libdexfile", "libdl_android", "libhidl-gen-utils", "libhidlbase", - "libhidltransport", - "libhwbinder", "libjsoncpp", "liblog", "liblzma", "libnativehelper", "libnetdbpf", "libnetdutils", + "libnetworkstatsfactorytestjni", "libpackagelistparser", "libpcre2", "libprocessgroup", "libselinux", - "libui", - "libutils", - "libvndksupport", "libtinyxml2", + "libui", "libunwindstack", + "libutils", "libutilscallstack", + "libvndksupport", "libziparchive", "libz", - "netd_aidl_interface-V2-cpp", - "libnetworkstatsfactorytestjni", + "netd_aidl_interface-cpp", ], } android_test { name: "FrameworksNetTests", defaults: ["FrameworksNetTests-jni-defaults"], - srcs: ["java/**/*.java", "java/**/*.kt"], + srcs: [ + "java/**/*.java", + "java/**/*.kt", + ], platform_apis: true, test_suites: ["device-tests"], certificate: "platform", + static_libs: [ + "androidx.test.rules", + "FrameworksNetCommonTests", + "frameworks-base-testutils", + "frameworks-net-integration-testutils", + "framework-protos", + "mockito-target-minus-junit4", + "net-tests-utils", + "platform-test-annotations", + "services.core", + "services.net", + ], + libs: [ + "android.test.runner", + "android.test.base", + "android.test.mock", + ], } diff --git a/tests/net/AndroidManifest.xml b/tests/net/AndroidManifest.xml index dcacb6dd7b10..009f817af407 100644 --- a/tests/net/AndroidManifest.xml +++ b/tests/net/AndroidManifest.xml @@ -32,7 +32,7 @@ <uses-permission android:name="android.permission.GET_DETAILED_TASKS" /> <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" /> <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" /> - <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" /> + <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.MANAGE_USERS" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> @@ -45,9 +45,13 @@ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" /> <uses-permission android:name="android.permission.INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.NETWORK_STACK" /> + <uses-permission android:name="android.permission.OBSERVE_NETWORK_POLICY" /> + <uses-permission android:name="android.permission.NETWORK_FACTORY" /> + <uses-permission android:name="android.permission.NETWORK_STATS_PROVIDER" /> <application> <uses-library android:name="android.test.runner" /> + <uses-library android:name="android.net.ipsec.ike" /> </application> <instrumentation diff --git a/tests/net/TEST_MAPPING b/tests/net/TEST_MAPPING new file mode 100644 index 000000000000..005cbe9ffdc4 --- /dev/null +++ b/tests/net/TEST_MAPPING @@ -0,0 +1,12 @@ +{ + "presubmit": [ + { + "name": "FrameworksNetIntegrationTests" + } + ], + "postsubmit": [ + { + "name": "FrameworksNetDeflakeTest" + } + ] +}
\ No newline at end of file diff --git a/tests/net/common/Android.bp b/tests/net/common/Android.bp index db1ccb446ce3..46d680fc4511 100644 --- a/tests/net/common/Android.bp +++ b/tests/net/common/Android.bp @@ -20,13 +20,14 @@ java_library { name: "FrameworksNetCommonTests", srcs: ["java/**/*.java", "java/**/*.kt"], static_libs: [ + "androidx.core_core", "androidx.test.rules", - "frameworks-net-testutils", "junit", "mockito-target-minus-junit4", + "net-tests-utils", "platform-test-annotations", ], libs: [ "android.test.base.stubs", ], -}
\ No newline at end of file +} diff --git a/tests/net/common/java/android/net/CaptivePortalDataTest.kt b/tests/net/common/java/android/net/CaptivePortalDataTest.kt new file mode 100644 index 000000000000..bd1847b7c440 --- /dev/null +++ b/tests/net/common/java/android/net/CaptivePortalDataTest.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net + +import android.os.Build +import androidx.test.filters.SmallTest +import com.android.testutils.assertParcelSane +import com.android.testutils.assertParcelingIsLossless +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo +import com.android.testutils.DevSdkIgnoreRunner +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals + +@SmallTest +@RunWith(DevSdkIgnoreRunner::class) +@IgnoreUpTo(Build.VERSION_CODES.Q) +class CaptivePortalDataTest { + private val data = CaptivePortalData.Builder() + .setRefreshTime(123L) + .setUserPortalUrl(Uri.parse("https://portal.example.com/test")) + .setVenueInfoUrl(Uri.parse("https://venue.example.com/test")) + .setSessionExtendable(true) + .setBytesRemaining(456L) + .setExpiryTime(789L) + .setCaptive(true) + .build() + + private fun makeBuilder() = CaptivePortalData.Builder(data) + + @Test + fun testParcelUnparcel() { + assertParcelSane(data, fieldCount = 7) + + assertParcelingIsLossless(makeBuilder().setUserPortalUrl(null).build()) + assertParcelingIsLossless(makeBuilder().setVenueInfoUrl(null).build()) + } + + @Test + fun testEquals() { + assertEquals(data, makeBuilder().build()) + + assertNotEqualsAfterChange { it.setRefreshTime(456L) } + assertNotEqualsAfterChange { it.setUserPortalUrl(Uri.parse("https://example.com/")) } + assertNotEqualsAfterChange { it.setUserPortalUrl(null) } + assertNotEqualsAfterChange { it.setVenueInfoUrl(Uri.parse("https://example.com/")) } + assertNotEqualsAfterChange { it.setVenueInfoUrl(null) } + assertNotEqualsAfterChange { it.setSessionExtendable(false) } + assertNotEqualsAfterChange { it.setBytesRemaining(789L) } + assertNotEqualsAfterChange { it.setExpiryTime(12L) } + assertNotEqualsAfterChange { it.setCaptive(false) } + } + + @Test + fun testUserPortalUrl() { + assertEquals(Uri.parse("https://portal.example.com/test"), data.userPortalUrl) + } + + @Test + fun testVenueInfoUrl() { + assertEquals(Uri.parse("https://venue.example.com/test"), data.venueInfoUrl) + } + + @Test + fun testIsSessionExtendable() { + assertTrue(data.isSessionExtendable) + } + + @Test + fun testByteLimit() { + assertEquals(456L, data.byteLimit) + // Test byteLimit unset. + assertEquals(-1L, CaptivePortalData.Builder(null).build().byteLimit) + } + + @Test + fun testRefreshTimeMillis() { + assertEquals(123L, data.refreshTimeMillis) + } + + @Test + fun testExpiryTimeMillis() { + assertEquals(789L, data.expiryTimeMillis) + // Test expiryTimeMillis unset. + assertEquals(-1L, CaptivePortalData.Builder(null).build().expiryTimeMillis) + } + + @Test + fun testIsCaptive() { + assertTrue(data.isCaptive) + assertFalse(makeBuilder().setCaptive(false).build().isCaptive) + } + + private fun CaptivePortalData.mutate(mutator: (CaptivePortalData.Builder) -> Unit) = + CaptivePortalData.Builder(this).apply { mutator(this) }.build() + + private fun assertNotEqualsAfterChange(mutator: (CaptivePortalData.Builder) -> Unit) { + assertNotEquals(data, data.mutate(mutator)) + } +}
\ No newline at end of file diff --git a/tests/net/common/java/android/net/CaptivePortalTest.java b/tests/net/common/java/android/net/CaptivePortalTest.java index eed7159ffddc..7a60cc105a26 100644 --- a/tests/net/common/java/android/net/CaptivePortalTest.java +++ b/tests/net/common/java/android/net/CaptivePortalTest.java @@ -18,19 +18,26 @@ package android.net; import static org.junit.Assert.assertEquals; +import android.os.Build; import android.os.RemoteException; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest public class CaptivePortalTest { + @Rule + public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(); + private static final int DEFAULT_TIMEOUT_MS = 5000; private static final String TEST_PACKAGE_NAME = "com.google.android.test"; @@ -44,6 +51,11 @@ public class CaptivePortalTest { } @Override + public void appRequest(final int request) throws RemoteException { + mCode = request; + } + + @Override public void logEvent(int eventId, String packageName) throws RemoteException { mCode = eventId; mPackageName = packageName; @@ -79,6 +91,13 @@ public class CaptivePortalTest { assertEquals(result.mCode, CaptivePortal.APP_RETURN_WANTED_AS_IS); } + @IgnoreUpTo(Build.VERSION_CODES.Q) + @Test + public void testReevaluateNetwork() { + final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.reevaluateNetwork()); + assertEquals(result.mCode, CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED); + } + @Test public void testLogEvent() { final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.logEvent( diff --git a/tests/net/common/java/android/net/DependenciesTest.java b/tests/net/common/java/android/net/DependenciesTest.java new file mode 100644 index 000000000000..ac1c28a45462 --- /dev/null +++ b/tests/net/common/java/android/net/DependenciesTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +/** + * A simple class that tests dependencies to java standard tools from the + * Network stack. These tests are not meant to be comprehensive tests of + * the relevant APIs : such tests belong in the relevant test suite for + * these dependencies. Instead, this just makes sure coverage is present + * by calling the methods in the exact way (or a representative way of how) + * they are called in the network stack. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DependenciesTest { + // Used to in ipmemorystore's RegularMaintenanceJobService to convert + // 24 hours into seconds + @Test + public void testTimeUnit() { + final int hours = 24; + final long inSeconds = TimeUnit.HOURS.toMillis(hours); + assertEquals(inSeconds, hours * 60 * 60 * 1000); + } + + private byte[] makeTrivialArray(final int size) { + final byte[] src = new byte[size]; + for (int i = 0; i < size; ++i) { + src[i] = (byte) i; + } + return src; + } + + // Used in ApfFilter to find an IP address from a byte array + @Test + public void testArrays() { + final int size = 128; + final byte[] src = makeTrivialArray(size); + + // Test copy + final int copySize = 16; + final int offset = 24; + final byte[] expected = new byte[copySize]; + for (int i = 0; i < copySize; ++i) { + expected[i] = (byte) (offset + i); + } + + final byte[] copy = Arrays.copyOfRange(src, offset, offset + copySize); + assertArrayEquals(expected, copy); + assertArrayEquals(new byte[0], Arrays.copyOfRange(src, size, size)); + } + + // Used mainly in the Dhcp code + @Test + public void testCopyOf() { + final byte[] src = makeTrivialArray(128); + final byte[] copy = Arrays.copyOf(src, src.length); + assertArrayEquals(src, copy); + assertFalse(src == copy); + + assertArrayEquals(new byte[0], Arrays.copyOf(src, 0)); + + final int excess = 16; + final byte[] biggerCopy = Arrays.copyOf(src, src.length + excess); + for (int i = src.length; i < src.length + excess; ++i) { + assertEquals(0, biggerCopy[i]); + } + for (int i = src.length - 1; i >= 0; --i) { + assertEquals(src[i], biggerCopy[i]); + } + } + + // Used mainly in DnsUtils but also various other places + @Test + public void testAsList() { + final int size = 24; + final Object[] src = new Object[size]; + final ArrayList<Object> expected = new ArrayList<>(size); + for (int i = 0; i < size; ++i) { + final Object o = new Object(); + src[i] = o; + expected.add(o); + } + assertEquals(expected, Arrays.asList(src)); + } +} diff --git a/tests/net/common/java/android/net/DhcpInfoTest.java b/tests/net/common/java/android/net/DhcpInfoTest.java new file mode 100644 index 000000000000..4d45ad72a9b8 --- /dev/null +++ b/tests/net/common/java/android/net/DhcpInfoTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2009 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; + +import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTL; +import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals; +import static com.android.testutils.ParcelUtilsKt.parcelingRoundTrip; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.annotation.Nullable; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; +import java.net.InetAddress; + +@RunWith(AndroidJUnit4.class) +public class DhcpInfoTest { + private static final String STR_ADDR1 = "255.255.255.255"; + private static final String STR_ADDR2 = "127.0.0.1"; + private static final String STR_ADDR3 = "192.168.1.1"; + private static final String STR_ADDR4 = "192.168.1.0"; + private static final int LEASE_TIME = 9999; + + private int ipToInteger(String ipString) throws Exception { + return inet4AddressToIntHTL((Inet4Address) InetAddress.getByName(ipString)); + } + + private DhcpInfo createDhcpInfoObject() throws Exception { + final DhcpInfo dhcpInfo = new DhcpInfo(); + dhcpInfo.ipAddress = ipToInteger(STR_ADDR1); + dhcpInfo.gateway = ipToInteger(STR_ADDR2); + dhcpInfo.netmask = ipToInteger(STR_ADDR3); + dhcpInfo.dns1 = ipToInteger(STR_ADDR4); + dhcpInfo.dns2 = ipToInteger(STR_ADDR4); + dhcpInfo.serverAddress = ipToInteger(STR_ADDR2); + dhcpInfo.leaseDuration = LEASE_TIME; + return dhcpInfo; + } + + @Test + public void testConstructor() { + new DhcpInfo(); + } + + @Test + public void testToString() throws Exception { + final String expectedDefault = "ipaddr 0.0.0.0 gateway 0.0.0.0 netmask 0.0.0.0 " + + "dns1 0.0.0.0 dns2 0.0.0.0 DHCP server 0.0.0.0 lease 0 seconds"; + + DhcpInfo dhcpInfo = new DhcpInfo(); + + // Test default string. + assertEquals(expectedDefault, dhcpInfo.toString()); + + dhcpInfo = createDhcpInfoObject(); + + final String expected = "ipaddr " + STR_ADDR1 + " gateway " + STR_ADDR2 + " netmask " + + STR_ADDR3 + " dns1 " + STR_ADDR4 + " dns2 " + STR_ADDR4 + " DHCP server " + + STR_ADDR2 + " lease " + LEASE_TIME + " seconds"; + // Test with new values + assertEquals(expected, dhcpInfo.toString()); + } + + private boolean dhcpInfoEquals(@Nullable DhcpInfo left, @Nullable DhcpInfo right) { + if (left == null && right == null) return true; + + if (left == null || right == null) return false; + + return left.ipAddress == right.ipAddress + && left.gateway == right.gateway + && left.netmask == right.netmask + && left.dns1 == right.dns1 + && left.dns2 == right.dns2 + && left.serverAddress == right.serverAddress + && left.leaseDuration == right.leaseDuration; + } + + @Test + public void testParcelDhcpInfo() throws Exception { + // Cannot use assertParcelSane() here because this requires .equals() to work as + // defined, but DhcpInfo has a different legacy behavior that we cannot change. + final DhcpInfo dhcpInfo = createDhcpInfoObject(); + assertFieldCountEquals(7, DhcpInfo.class); + + final DhcpInfo dhcpInfoRoundTrip = parcelingRoundTrip(dhcpInfo); + assertTrue(dhcpInfoEquals(null, null)); + assertFalse(dhcpInfoEquals(null, dhcpInfoRoundTrip)); + assertFalse(dhcpInfoEquals(dhcpInfo, null)); + assertTrue(dhcpInfoEquals(dhcpInfo, dhcpInfoRoundTrip)); + } +} diff --git a/tests/net/common/java/android/net/IpPrefixTest.java b/tests/net/common/java/android/net/IpPrefixTest.java index 719960d48604..985e10df3961 100644 --- a/tests/net/common/java/android/net/IpPrefixTest.java +++ b/tests/net/common/java/android/net/IpPrefixTest.java @@ -16,16 +16,18 @@ package android.net; +import static com.android.testutils.MiscAssertsKt.assertEqualBothWays; +import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals; +import static com.android.testutils.MiscAssertsKt.assertNotEqualEitherWay; +import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless; + import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import android.os.Parcel; - import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -171,56 +173,46 @@ public class IpPrefixTest { } - private void assertAreEqual(Object o1, Object o2) { - assertTrue(o1.equals(o2)); - assertTrue(o2.equals(o1)); - } - - private void assertAreNotEqual(Object o1, Object o2) { - assertFalse(o1.equals(o2)); - assertFalse(o2.equals(o1)); - } - @Test public void testEquals() { IpPrefix p1, p2; p1 = new IpPrefix("192.0.2.251/23"); p2 = new IpPrefix(new byte[]{(byte) 192, (byte) 0, (byte) 2, (byte) 251}, 23); - assertAreEqual(p1, p2); + assertEqualBothWays(p1, p2); p1 = new IpPrefix("192.0.2.5/23"); - assertAreEqual(p1, p2); + assertEqualBothWays(p1, p2); p1 = new IpPrefix("192.0.2.5/24"); - assertAreNotEqual(p1, p2); + assertNotEqualEitherWay(p1, p2); p1 = new IpPrefix("192.0.4.5/23"); - assertAreNotEqual(p1, p2); + assertNotEqualEitherWay(p1, p2); p1 = new IpPrefix("2001:db8:dead:beef:f00::80/122"); p2 = new IpPrefix(IPV6_BYTES, 122); assertEquals("2001:db8:dead:beef:f00::80/122", p2.toString()); - assertAreEqual(p1, p2); + assertEqualBothWays(p1, p2); p1 = new IpPrefix("2001:db8:dead:beef:f00::bf/122"); - assertAreEqual(p1, p2); + assertEqualBothWays(p1, p2); p1 = new IpPrefix("2001:db8:dead:beef:f00::8:0/123"); - assertAreNotEqual(p1, p2); + assertNotEqualEitherWay(p1, p2); p1 = new IpPrefix("2001:db8:dead:beef::/122"); - assertAreNotEqual(p1, p2); + assertNotEqualEitherWay(p1, p2); // 192.0.2.4/32 != c000:0204::/32. byte[] ipv6bytes = new byte[16]; System.arraycopy(IPV4_BYTES, 0, ipv6bytes, 0, IPV4_BYTES.length); p1 = new IpPrefix(ipv6bytes, 32); - assertAreEqual(p1, new IpPrefix("c000:0204::/32")); + assertEqualBothWays(p1, new IpPrefix("c000:0204::/32")); p2 = new IpPrefix(IPV4_BYTES, 32); - assertAreNotEqual(p1, p2); + assertNotEqualEitherWay(p1, p2); } @Test @@ -356,25 +348,6 @@ public class IpPrefixTest { assertEquals(InetAddress.parseNumericAddress("192.0.2.0"), p.getAddress()); } - public IpPrefix passThroughParcel(IpPrefix p) { - Parcel parcel = Parcel.obtain(); - IpPrefix p2 = null; - try { - p.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - p2 = IpPrefix.CREATOR.createFromParcel(parcel); - } finally { - parcel.recycle(); - } - assertNotNull(p2); - return p2; - } - - public void assertParcelingIsLossless(IpPrefix p) { - IpPrefix p2 = passThroughParcel(p); - assertEquals(p, p2); - } - @Test public void testParceling() { IpPrefix p; @@ -386,5 +359,7 @@ public class IpPrefixTest { p = new IpPrefix("192.0.2.0/25"); assertParcelingIsLossless(p); assertTrue(p.isIPv4()); + + assertFieldCountEquals(2, IpPrefix.class); } } diff --git a/tests/net/common/java/android/net/KeepalivePacketDataTest.kt b/tests/net/common/java/android/net/KeepalivePacketDataTest.kt new file mode 100644 index 000000000000..f464ec6cf0e5 --- /dev/null +++ b/tests/net/common/java/android/net/KeepalivePacketDataTest.kt @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net + +import android.net.InvalidPacketException.ERROR_INVALID_IP_ADDRESS +import android.net.InvalidPacketException.ERROR_INVALID_PORT +import android.os.Build +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo +import java.net.InetAddress +import java.util.Arrays +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Assert.fail +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class KeepalivePacketDataTest { + @Rule @JvmField + val ignoreRule: DevSdkIgnoreRule = DevSdkIgnoreRule() + + private val INVALID_PORT = 65537 + private val TEST_DST_PORT = 4244 + private val TEST_SRC_PORT = 4243 + + private val TESTBYTES = byteArrayOf(12, 31, 22, 44) + private val TEST_SRC_ADDRV4 = "198.168.0.2".address() + private val TEST_DST_ADDRV4 = "198.168.0.1".address() + private val TEST_ADDRV6 = "2001:db8::1".address() + + private fun String.address() = InetAddresses.parseNumericAddress(this) + + // Add for test because constructor of KeepalivePacketData is protected. + private inner class TestKeepalivePacketData( + srcAddress: InetAddress? = TEST_SRC_ADDRV4, + srcPort: Int = TEST_SRC_PORT, + dstAddress: InetAddress? = TEST_DST_ADDRV4, + dstPort: Int = TEST_DST_PORT, + data: ByteArray = TESTBYTES + ) : KeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, data) + + @Test + @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testConstructor() { + var data: TestKeepalivePacketData + + try { + data = TestKeepalivePacketData(srcAddress = null) + fail("Null src address should cause exception") + } catch (e: InvalidPacketException) { + assertEquals(e.error, ERROR_INVALID_IP_ADDRESS) + } + + try { + data = TestKeepalivePacketData(dstAddress = null) + fail("Null dst address should cause exception") + } catch (e: InvalidPacketException) { + assertEquals(e.error, ERROR_INVALID_IP_ADDRESS) + } + + try { + data = TestKeepalivePacketData(dstAddress = TEST_ADDRV6) + fail("Ip family mismatched should cause exception") + } catch (e: InvalidPacketException) { + assertEquals(e.error, ERROR_INVALID_IP_ADDRESS) + } + + try { + data = TestKeepalivePacketData(srcPort = INVALID_PORT) + fail("Invalid srcPort should cause exception") + } catch (e: InvalidPacketException) { + assertEquals(e.error, ERROR_INVALID_PORT) + } + + try { + data = TestKeepalivePacketData(dstPort = INVALID_PORT) + fail("Invalid dstPort should cause exception") + } catch (e: InvalidPacketException) { + assertEquals(e.error, ERROR_INVALID_PORT) + } + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testSrcAddress() = assertEquals(TEST_SRC_ADDRV4, TestKeepalivePacketData().srcAddress) + + @Test + @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testDstAddress() = assertEquals(TEST_DST_ADDRV4, TestKeepalivePacketData().dstAddress) + + @Test + @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testSrcPort() = assertEquals(TEST_SRC_PORT, TestKeepalivePacketData().srcPort) + + @Test + @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testDstPort() = assertEquals(TEST_DST_PORT, TestKeepalivePacketData().dstPort) + + @Test + @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testPacket() = assertTrue(Arrays.equals(TESTBYTES, TestKeepalivePacketData().packet)) +}
\ No newline at end of file diff --git a/tests/net/common/java/android/net/LinkAddressTest.java b/tests/net/common/java/android/net/LinkAddressTest.java index d462441b22fa..c74c112490f8 100644 --- a/tests/net/common/java/android/net/LinkAddressTest.java +++ b/tests/net/common/java/android/net/LinkAddressTest.java @@ -27,18 +27,28 @@ import static android.system.OsConstants.RT_SCOPE_LINK; import static android.system.OsConstants.RT_SCOPE_SITE; import static android.system.OsConstants.RT_SCOPE_UNIVERSE; +import static com.android.testutils.MiscAssertsKt.assertEqualBothWays; +import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals; +import static com.android.testutils.MiscAssertsKt.assertNotEqualEitherWay; +import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import android.os.Parcel; +import android.os.Build; +import android.os.SystemClock; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; + +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -53,6 +63,8 @@ import java.util.List; @RunWith(AndroidJUnit4.class) @SmallTest public class LinkAddressTest { + @Rule + public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(); private static final String V4 = "192.0.2.1"; private static final String V6 = "2001:db8::1"; @@ -217,67 +229,56 @@ public class LinkAddressTest { l1.isSameAddressAs(l2)); } - private void assertLinkAddressesEqual(LinkAddress l1, LinkAddress l2) { - assertTrue(l1 + " unexpectedly not equal to " + l2, l1.equals(l2)); - assertTrue(l2 + " unexpectedly not equal to " + l1, l2.equals(l1)); - assertEquals(l1.hashCode(), l2.hashCode()); - } - - private void assertLinkAddressesNotEqual(LinkAddress l1, LinkAddress l2) { - assertFalse(l1 + " unexpectedly equal to " + l2, l1.equals(l2)); - assertFalse(l2 + " unexpectedly equal to " + l1, l2.equals(l1)); - } - @Test public void testEqualsAndSameAddressAs() { LinkAddress l1, l2, l3; l1 = new LinkAddress("2001:db8::1/64"); l2 = new LinkAddress("2001:db8::1/64"); - assertLinkAddressesEqual(l1, l2); + assertEqualBothWays(l1, l2); assertIsSameAddressAs(l1, l2); l2 = new LinkAddress("2001:db8::1/65"); - assertLinkAddressesNotEqual(l1, l2); + assertNotEqualEitherWay(l1, l2); assertIsNotSameAddressAs(l1, l2); l2 = new LinkAddress("2001:db8::2/64"); - assertLinkAddressesNotEqual(l1, l2); + assertNotEqualEitherWay(l1, l2); assertIsNotSameAddressAs(l1, l2); l1 = new LinkAddress("192.0.2.1/24"); l2 = new LinkAddress("192.0.2.1/24"); - assertLinkAddressesEqual(l1, l2); + assertEqualBothWays(l1, l2); assertIsSameAddressAs(l1, l2); l2 = new LinkAddress("192.0.2.1/23"); - assertLinkAddressesNotEqual(l1, l2); + assertNotEqualEitherWay(l1, l2); assertIsNotSameAddressAs(l1, l2); l2 = new LinkAddress("192.0.2.2/24"); - assertLinkAddressesNotEqual(l1, l2); + assertNotEqualEitherWay(l1, l2); assertIsNotSameAddressAs(l1, l2); // Check equals() and isSameAddressAs() on identical addresses with different flags. l1 = new LinkAddress(V6_ADDRESS, 64); l2 = new LinkAddress(V6_ADDRESS, 64, 0, RT_SCOPE_UNIVERSE); - assertLinkAddressesEqual(l1, l2); + assertEqualBothWays(l1, l2); assertIsSameAddressAs(l1, l2); l2 = new LinkAddress(V6_ADDRESS, 64, IFA_F_DEPRECATED, RT_SCOPE_UNIVERSE); - assertLinkAddressesNotEqual(l1, l2); + assertNotEqualEitherWay(l1, l2); assertIsSameAddressAs(l1, l2); // Check equals() and isSameAddressAs() on identical addresses with different scope. l1 = new LinkAddress(V4_ADDRESS, 24); l2 = new LinkAddress(V4_ADDRESS, 24, 0, RT_SCOPE_UNIVERSE); - assertLinkAddressesEqual(l1, l2); + assertEqualBothWays(l1, l2); assertIsSameAddressAs(l1, l2); l2 = new LinkAddress(V4_ADDRESS, 24, 0, RT_SCOPE_HOST); - assertLinkAddressesNotEqual(l1, l2); + assertNotEqualEitherWay(l1, l2); assertIsSameAddressAs(l1, l2); // Addresses with the same start or end bytes aren't equal between families. @@ -291,10 +292,10 @@ public class LinkAddressTest { assertTrue(Arrays.equals(ipv4Bytes, l2FirstIPv6Bytes)); assertTrue(Arrays.equals(ipv4Bytes, l3LastIPv6Bytes)); - assertLinkAddressesNotEqual(l1, l2); + assertNotEqualEitherWay(l1, l2); assertIsNotSameAddressAs(l1, l2); - assertLinkAddressesNotEqual(l1, l3); + assertNotEqualEitherWay(l1, l3); assertIsNotSameAddressAs(l1, l3); // Because we use InetAddress, an IPv4 address is equal to its IPv4-mapped address. @@ -302,7 +303,7 @@ public class LinkAddressTest { String addressString = V4 + "/24"; l1 = new LinkAddress(addressString); l2 = new LinkAddress("::ffff:" + addressString); - assertLinkAddressesEqual(l1, l2); + assertEqualBothWays(l1, l2); assertIsSameAddressAs(l1, l2); } @@ -319,25 +320,6 @@ public class LinkAddressTest { assertNotEquals(l1.hashCode(), l2.hashCode()); } - private LinkAddress passThroughParcel(LinkAddress l) { - Parcel p = Parcel.obtain(); - LinkAddress l2 = null; - try { - l.writeToParcel(p, 0); - p.setDataPosition(0); - l2 = LinkAddress.CREATOR.createFromParcel(p); - } finally { - p.recycle(); - } - assertNotNull(l2); - return l2; - } - - private void assertParcelingIsLossless(LinkAddress l) { - LinkAddress l2 = passThroughParcel(l); - assertEquals(l, l2); - } - @Test public void testParceling() { LinkAddress l; @@ -349,6 +331,96 @@ public class LinkAddressTest { assertParcelingIsLossless(l); } + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testLifetimeParceling() { + final LinkAddress l = new LinkAddress(V6_ADDRESS, 64, 123, 456, 1L, 3600000L); + assertParcelingIsLossless(l); + } + + @Test @IgnoreAfter(Build.VERSION_CODES.Q) + public void testFieldCount_Q() { + assertFieldCountEquals(4, LinkAddress.class); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testFieldCount() { + // Make sure any new field is covered by the above parceling tests when changing this number + assertFieldCountEquals(6, LinkAddress.class); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testDeprecationTime() { + try { + new LinkAddress(V6_ADDRESS, 64, 0, 456, + LinkAddress.LIFETIME_UNKNOWN, 100000L); + fail("Only one time provided should cause exception"); + } catch (IllegalArgumentException expected) { } + + try { + new LinkAddress(V6_ADDRESS, 64, 0, 456, + 200000L, 100000L); + fail("deprecation time later than expiration time should cause exception"); + } catch (IllegalArgumentException expected) { } + + try { + new LinkAddress(V6_ADDRESS, 64, 0, 456, + -2, 100000L); + fail("negative deprecation time should cause exception"); + } catch (IllegalArgumentException expected) { } + + LinkAddress addr = new LinkAddress(V6_ADDRESS, 64, 0, 456, 100000L, 200000L); + assertEquals(100000L, addr.getDeprecationTime()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testExpirationTime() { + try { + new LinkAddress(V6_ADDRESS, 64, 0, 456, + 200000L, LinkAddress.LIFETIME_UNKNOWN); + fail("Only one time provided should cause exception"); + } catch (IllegalArgumentException expected) { } + + try { + new LinkAddress(V6_ADDRESS, 64, 0, 456, + 100000L, -2); + fail("negative expiration time should cause exception"); + } catch (IllegalArgumentException expected) { } + + LinkAddress addr = new LinkAddress(V6_ADDRESS, 64, 0, 456, 100000L, 200000L); + assertEquals(200000L, addr.getExpirationTime()); + } + + @Test + public void testGetFlags() { + LinkAddress l = new LinkAddress(V6_ADDRESS, 64, 123, RT_SCOPE_HOST); + assertEquals(123, l.getFlags()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testGetFlags_Deprecation() { + // Test if deprecated bit was added/remove automatically based on the provided deprecation + // time + LinkAddress l = new LinkAddress(V6_ADDRESS, 64, 0, RT_SCOPE_HOST, + 1L, LinkAddress.LIFETIME_PERMANENT); + // Check if the flag is added automatically. + assertTrue((l.getFlags() & IFA_F_DEPRECATED) != 0); + + l = new LinkAddress(V6_ADDRESS, 64, IFA_F_DEPRECATED, RT_SCOPE_HOST, + SystemClock.elapsedRealtime() + 100000L, LinkAddress.LIFETIME_PERMANENT); + // Check if the flag is removed automatically. + assertTrue((l.getFlags() & IFA_F_DEPRECATED) == 0); + + l = new LinkAddress(V6_ADDRESS, 64, IFA_F_DEPRECATED, RT_SCOPE_HOST, + LinkAddress.LIFETIME_PERMANENT, LinkAddress.LIFETIME_PERMANENT); + // Check if the permanent flag is added. + assertTrue((l.getFlags() & IFA_F_PERMANENT) != 0); + + l = new LinkAddress(V6_ADDRESS, 64, IFA_F_PERMANENT, RT_SCOPE_HOST, + 1000L, SystemClock.elapsedRealtime() + 100000L); + // Check if the permanent flag is removed + assertTrue((l.getFlags() & IFA_F_PERMANENT) == 0); + } + private void assertGlobalPreferred(LinkAddress l, String msg) { assertTrue(msg, l.isGlobalPreferred()); } @@ -418,4 +490,14 @@ public class LinkAddressTest { RT_SCOPE_UNIVERSE); assertGlobalPreferred(l, "v6,global,tempaddr+optimistic"); } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testIsGlobalPreferred_DeprecatedInFuture() { + final LinkAddress l = new LinkAddress(V6_ADDRESS, 64, IFA_F_DEPRECATED, + RT_SCOPE_UNIVERSE, SystemClock.elapsedRealtime() + 100000, + SystemClock.elapsedRealtime() + 200000); + // Although the deprecated bit is set, but the deprecation time is in the future, test + // if the flag is removed automatically. + assertGlobalPreferred(l, "v6,global,tempaddr+deprecated in the future"); + } } diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java index 388e9eb8e960..6eba62e63740 100644 --- a/tests/net/common/java/android/net/LinkPropertiesTest.java +++ b/tests/net/common/java/android/net/LinkPropertiesTest.java @@ -16,6 +16,14 @@ package android.net; +import static android.net.RouteInfo.RTN_THROW; +import static android.net.RouteInfo.RTN_UNICAST; +import static android.net.RouteInfo.RTN_UNREACHABLE; + +import static com.android.testutils.ParcelUtilsKt.assertParcelSane; +import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless; +import static com.android.testutils.ParcelUtilsKt.parcelingRoundTrip; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -23,20 +31,26 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import android.net.LinkProperties.CompareResult; import android.net.LinkProperties.ProvisioningChange; +import android.net.util.LinkPropertiesUtils.CompareResult; +import android.os.Build; import android.system.OsConstants; import android.util.ArraySet; +import androidx.core.os.BuildCompat; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.util.ParcelableTestUtil; -import com.android.internal.util.TestUtils; +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; @@ -48,6 +62,9 @@ import java.util.Set; @RunWith(AndroidJUnit4.class) @SmallTest public class LinkPropertiesTest { + @Rule + public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(); + private static final InetAddress ADDRV4 = address("75.208.6.1"); private static final InetAddress ADDRV6 = address("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); private static final InetAddress DNS1 = address("75.208.7.1"); @@ -64,6 +81,7 @@ public class LinkPropertiesTest { private static final InetAddress GATEWAY62 = address("fe80::6:22%lo"); private static final InetAddress TESTIPV4ADDR = address("192.168.47.42"); private static final InetAddress TESTIPV6ADDR = address("fe80::7:33%43"); + private static final Inet4Address DHCPSERVER = (Inet4Address) address("192.0.2.1"); private static final String NAME = "qmi0"; private static final String DOMAINS = "google.com"; private static final String PRIV_DNS_SERVER_NAME = "private.dns.com"; @@ -72,11 +90,24 @@ public class LinkPropertiesTest { private static final LinkAddress LINKADDRV4 = new LinkAddress(ADDRV4, 32); private static final LinkAddress LINKADDRV6 = new LinkAddress(ADDRV6, 128); private static final LinkAddress LINKADDRV6LINKLOCAL = new LinkAddress("fe80::1/64"); + private static final Uri CAPPORT_API_URL = Uri.parse("https://test.example.com/capportapi"); + + // CaptivePortalData cannot be in a constant as it does not exist on Q. + // The test runner also crashes when scanning for tests if it is a return type. + private static Object getCaptivePortalData() { + return new CaptivePortalData.Builder() + .setVenueInfoUrl(Uri.parse("https://test.example.com/venue")).build(); + } private static InetAddress address(String addrString) { return InetAddresses.parseNumericAddress(addrString); } + private static boolean isAtLeastR() { + // BuildCompat.isAtLeastR is documented to return false on release SDKs (including R) + return Build.VERSION.SDK_INT > Build.VERSION_CODES.Q || BuildCompat.isAtLeastR(); + } + private void checkEmpty(final LinkProperties lp) { assertEquals(0, lp.getAllInterfaceNames().size()); assertEquals(0, lp.getAllAddresses().size()); @@ -96,6 +127,13 @@ public class LinkPropertiesTest { assertFalse(lp.isIpv4Provisioned()); assertFalse(lp.isIpv6Provisioned()); assertFalse(lp.isPrivateDnsActive()); + + if (isAtLeastR()) { + assertNull(lp.getDhcpServerAddress()); + assertFalse(lp.isWakeOnLanSupported()); + assertNull(lp.getCaptivePortalApiUrl()); + assertNull(lp.getCaptivePortalData()); + } } private LinkProperties makeTestObject() { @@ -117,6 +155,12 @@ public class LinkPropertiesTest { lp.setMtu(MTU); lp.setTcpBufferSizes(TCP_BUFFER_SIZES); lp.setNat64Prefix(new IpPrefix("2001:db8:0:64::/96")); + if (isAtLeastR()) { + lp.setDhcpServerAddress(DHCPSERVER); + lp.setWakeOnLanSupported(true); + lp.setCaptivePortalApiUrl(CAPPORT_API_URL); + lp.setCaptivePortalData((CaptivePortalData) getCaptivePortalData()); + } return lp; } @@ -155,6 +199,20 @@ public class LinkPropertiesTest { assertTrue(source.isIdenticalTcpBufferSizes(target)); assertTrue(target.isIdenticalTcpBufferSizes(source)); + if (isAtLeastR()) { + assertTrue(source.isIdenticalDhcpServerAddress(target)); + assertTrue(source.isIdenticalDhcpServerAddress(source)); + + assertTrue(source.isIdenticalWakeOnLan(target)); + assertTrue(target.isIdenticalWakeOnLan(source)); + + assertTrue(source.isIdenticalCaptivePortalApiUrl(target)); + assertTrue(target.isIdenticalCaptivePortalApiUrl(source)); + + assertTrue(source.isIdenticalCaptivePortalData(target)); + assertTrue(target.isIdenticalCaptivePortalData(source)); + } + // Check result of equals(). assertTrue(source.equals(target)); assertTrue(target.equals(source)); @@ -292,7 +350,7 @@ public class LinkPropertiesTest { source.addDnsServer(DNS1); source.addDnsServer(DNS2); // set 2 gateways - source.addRoute(new RouteInfo(GATEWAY1)); + source.addRoute(new RouteInfo(LINKADDRV4, GATEWAY1)); source.addRoute(new RouteInfo(GATEWAY2)); source.setMtu(MTU); @@ -304,7 +362,7 @@ public class LinkPropertiesTest { target.addDnsServer(DNS2); target.addDnsServer(DNS1); target.addRoute(new RouteInfo(GATEWAY2)); - target.addRoute(new RouteInfo(GATEWAY1)); + target.addRoute(new RouteInfo(LINKADDRV4, GATEWAY1)); target.setMtu(MTU); assertLinkPropertiesEqual(source, target); @@ -341,12 +399,13 @@ public class LinkPropertiesTest { @Test public void testRouteInterfaces() { - LinkAddress prefix = new LinkAddress(address("2001:db8::"), 32); + LinkAddress prefix1 = new LinkAddress(address("2001:db8:1::"), 48); + LinkAddress prefix2 = new LinkAddress(address("2001:db8:2::"), 48); InetAddress address = ADDRV6; // Add a route with no interface to a LinkProperties with no interface. No errors. LinkProperties lp = new LinkProperties(); - RouteInfo r = new RouteInfo(prefix, address, null); + RouteInfo r = new RouteInfo(prefix1, address, null); assertTrue(lp.addRoute(r)); assertEquals(1, lp.getRoutes().size()); assertAllRoutesHaveInterface(null, lp); @@ -356,7 +415,7 @@ public class LinkPropertiesTest { assertEquals(1, lp.getRoutes().size()); // Add a route with an interface. Expect an exception. - r = new RouteInfo(prefix, address, "wlan0"); + r = new RouteInfo(prefix2, address, "wlan0"); try { lp.addRoute(r); fail("Adding wlan0 route to LP with no interface, expect exception"); @@ -375,7 +434,7 @@ public class LinkPropertiesTest { } catch (IllegalArgumentException expected) {} // If the interface name matches, the route is added. - r = new RouteInfo(prefix, null, "wlan0"); + r = new RouteInfo(prefix2, null, "wlan0"); lp.setInterfaceName("wlan0"); lp.addRoute(r); assertEquals(2, lp.getRoutes().size()); @@ -391,19 +450,27 @@ public class LinkPropertiesTest { // Check comparisons work. LinkProperties lp2 = new LinkProperties(lp); assertAllRoutesHaveInterface("wlan0", lp2); - assertEquals(0, lp.compareAllRoutes(lp2).added.size()); - assertEquals(0, lp.compareAllRoutes(lp2).removed.size()); + // LinkProperties#compareAllRoutes exists both in R and before R, but the return type + // changed in R, so a test compiled with the R version of LinkProperties cannot run on Q. + if (isAtLeastR()) { + assertEquals(0, lp.compareAllRoutes(lp2).added.size()); + assertEquals(0, lp.compareAllRoutes(lp2).removed.size()); + } lp2.setInterfaceName("p2p0"); assertAllRoutesHaveInterface("p2p0", lp2); assertAllRoutesNotHaveInterface("wlan0", lp2); - assertEquals(3, lp.compareAllRoutes(lp2).added.size()); - assertEquals(3, lp.compareAllRoutes(lp2).removed.size()); + if (isAtLeastR()) { + assertEquals(3, lp.compareAllRoutes(lp2).added.size()); + assertEquals(3, lp.compareAllRoutes(lp2).removed.size()); + } - // Check remove works - lp.removeRoute(new RouteInfo(prefix, address, null)); + // Remove route with incorrect interface, no route removed. + lp.removeRoute(new RouteInfo(prefix2, null, null)); assertEquals(3, lp.getRoutes().size()); - lp.removeRoute(new RouteInfo(prefix, address, "wlan0")); + + // Check remove works when interface is correct. + lp.removeRoute(new RouteInfo(prefix2, null, "wlan0")); assertEquals(2, lp.getRoutes().size()); assertAllRoutesHaveInterface("wlan0", lp); assertAllRoutesNotHaveInterface("p2p0", lp); @@ -424,6 +491,8 @@ public class LinkPropertiesTest { assertEquals(1, rmnet0.getLinkAddresses().size()); assertEquals(1, rmnet0.getAllAddresses().size()); assertEquals(1, rmnet0.getAllLinkAddresses().size()); + assertEquals(1, rmnet0.getAllInterfaceNames().size()); + assertEquals("rmnet0", rmnet0.getAllInterfaceNames().get(0)); rmnet0.addStackedLink(clat4); assertEquals(1, rmnet0.getStackedLinks().size()); @@ -431,6 +500,9 @@ public class LinkPropertiesTest { assertEquals(1, rmnet0.getLinkAddresses().size()); assertEquals(2, rmnet0.getAllAddresses().size()); assertEquals(2, rmnet0.getAllLinkAddresses().size()); + assertEquals(2, rmnet0.getAllInterfaceNames().size()); + assertEquals("rmnet0", rmnet0.getAllInterfaceNames().get(0)); + assertEquals("clat4", rmnet0.getAllInterfaceNames().get(1)); rmnet0.addStackedLink(clat4); assertEquals(1, rmnet0.getStackedLinks().size()); @@ -438,6 +510,9 @@ public class LinkPropertiesTest { assertEquals(1, rmnet0.getLinkAddresses().size()); assertEquals(2, rmnet0.getAllAddresses().size()); assertEquals(2, rmnet0.getAllLinkAddresses().size()); + assertEquals(2, rmnet0.getAllInterfaceNames().size()); + assertEquals("rmnet0", rmnet0.getAllInterfaceNames().get(0)); + assertEquals("clat4", rmnet0.getAllInterfaceNames().get(1)); assertEquals(0, clat4.getStackedLinks().size()); @@ -457,6 +532,8 @@ public class LinkPropertiesTest { assertEquals(1, rmnet0.getLinkAddresses().size()); assertEquals(1, rmnet0.getAllAddresses().size()); assertEquals(1, rmnet0.getAllLinkAddresses().size()); + assertEquals(1, rmnet0.getAllInterfaceNames().size()); + assertEquals("rmnet0", rmnet0.getAllInterfaceNames().get(0)); assertFalse(rmnet0.removeStackedLink("clat4")); } @@ -880,7 +957,7 @@ public class LinkPropertiesTest { } - @Test + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) public void testCompareResult() { // Either adding or removing items compareResult(Arrays.asList(1, 2, 3, 4), Arrays.asList(1), @@ -917,8 +994,7 @@ public class LinkPropertiesTest { assertEquals(new ArraySet<>(expectRemoved), (new ArraySet<>(result.removed))); } - @Test - public void testLinkPropertiesParcelable() throws Exception { + private static LinkProperties makeLinkPropertiesForParceling() { LinkProperties source = new LinkProperties(); source.setInterfaceName(NAME); @@ -956,16 +1032,40 @@ public class LinkPropertiesTest { stacked.setInterfaceName("test-stacked"); source.addStackedLink(stacked); - TestUtils.assertParcelingIsLossless(source); - ParcelableTestUtil.assertFieldCountEquals(14, LinkProperties.class); + return source; } - @Test + @Test @IgnoreAfter(Build.VERSION_CODES.Q) + public void testLinkPropertiesParcelable_Q() throws Exception { + final LinkProperties source = makeLinkPropertiesForParceling(); + assertParcelSane(source, 14 /* fieldCount */); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testLinkPropertiesParcelable() throws Exception { + final LinkProperties source = makeLinkPropertiesForParceling(); + + source.setWakeOnLanSupported(true); + source.setCaptivePortalApiUrl(CAPPORT_API_URL); + source.setCaptivePortalData((CaptivePortalData) getCaptivePortalData()); + source.setDhcpServerAddress((Inet4Address) GATEWAY1); + assertParcelSane(new LinkProperties(source, true /* parcelSensitiveFields */), + 18 /* fieldCount */); + + // Verify that without using a sensitiveFieldsParcelingCopy, sensitive fields are cleared. + final LinkProperties sanitized = new LinkProperties(source); + sanitized.setCaptivePortalApiUrl(null); + sanitized.setCaptivePortalData(null); + assertEquals(sanitized, parcelingRoundTrip(source)); + } + + // Parceling of the scope was broken until Q-QPR2 + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) public void testLinkLocalDnsServerParceling() throws Exception { final String strAddress = "fe80::1%lo"; final LinkProperties lp = new LinkProperties(); lp.addDnsServer(address(strAddress)); - final LinkProperties unparceled = TestUtils.parcelingRoundTrip(lp); + final LinkProperties unparceled = parcelingRoundTrip(lp); // Inet6Address#equals does not test for the scope id assertEquals(strAddress, unparceled.getDnsServers().get(0).getHostAddress()); } @@ -973,7 +1073,7 @@ public class LinkPropertiesTest { @Test public void testParcelUninitialized() throws Exception { LinkProperties empty = new LinkProperties(); - TestUtils.assertParcelingIsLossless(empty); + assertParcelingIsLossless(empty); } @Test @@ -1082,4 +1182,122 @@ public class LinkPropertiesTest { lp.clear(); assertFalse(lp.isPrivateDnsActive()); } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testDhcpServerAddress() { + final LinkProperties lp = makeTestObject(); + assertEquals(DHCPSERVER, lp.getDhcpServerAddress()); + + lp.clear(); + assertNull(lp.getDhcpServerAddress()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testWakeOnLanSupported() { + final LinkProperties lp = makeTestObject(); + assertTrue(lp.isWakeOnLanSupported()); + + lp.clear(); + assertFalse(lp.isWakeOnLanSupported()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testCaptivePortalApiUrl() { + final LinkProperties lp = makeTestObject(); + assertEquals(CAPPORT_API_URL, lp.getCaptivePortalApiUrl()); + + lp.clear(); + assertNull(lp.getCaptivePortalApiUrl()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testCaptivePortalData() { + final LinkProperties lp = makeTestObject(); + assertEquals(getCaptivePortalData(), lp.getCaptivePortalData()); + + lp.clear(); + assertNull(lp.getCaptivePortalData()); + } + + private LinkProperties makeIpv4LinkProperties() { + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setInterfaceName(NAME); + linkProperties.addLinkAddress(LINKADDRV4); + linkProperties.addDnsServer(DNS1); + linkProperties.addRoute(new RouteInfo(GATEWAY1)); + linkProperties.addRoute(new RouteInfo(GATEWAY2)); + return linkProperties; + } + + private LinkProperties makeIpv6LinkProperties() { + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setInterfaceName(NAME); + linkProperties.addLinkAddress(LINKADDRV6); + linkProperties.addDnsServer(DNS6); + linkProperties.addRoute(new RouteInfo(GATEWAY61)); + linkProperties.addRoute(new RouteInfo(GATEWAY62)); + return linkProperties; + } + + @Test + public void testHasIpv4DefaultRoute() { + final LinkProperties Ipv4 = makeIpv4LinkProperties(); + assertTrue(Ipv4.hasIpv4DefaultRoute()); + final LinkProperties Ipv6 = makeIpv6LinkProperties(); + assertFalse(Ipv6.hasIpv4DefaultRoute()); + } + + @Test + public void testHasIpv4DnsServer() { + final LinkProperties Ipv4 = makeIpv4LinkProperties(); + assertTrue(Ipv4.hasIpv4DnsServer()); + final LinkProperties Ipv6 = makeIpv6LinkProperties(); + assertFalse(Ipv6.hasIpv4DnsServer()); + } + + @Test + public void testHasIpv6DnsServer() { + final LinkProperties Ipv4 = makeIpv4LinkProperties(); + assertFalse(Ipv4.hasIpv6DnsServer()); + final LinkProperties Ipv6 = makeIpv6LinkProperties(); + assertTrue(Ipv6.hasIpv6DnsServer()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testHasIpv4UnreachableDefaultRoute() { + final LinkProperties lp = makeTestObject(); + assertFalse(lp.hasIpv4UnreachableDefaultRoute()); + assertFalse(lp.hasIpv6UnreachableDefaultRoute()); + + lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); + assertTrue(lp.hasIpv4UnreachableDefaultRoute()); + assertFalse(lp.hasIpv6UnreachableDefaultRoute()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testHasIpv6UnreachableDefaultRoute() { + final LinkProperties lp = makeTestObject(); + assertFalse(lp.hasIpv6UnreachableDefaultRoute()); + assertFalse(lp.hasIpv4UnreachableDefaultRoute()); + + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE)); + assertTrue(lp.hasIpv6UnreachableDefaultRoute()); + assertFalse(lp.hasIpv4UnreachableDefaultRoute()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testRouteAddWithSameKey() throws Exception { + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("wlan0"); + final IpPrefix v6 = new IpPrefix("64:ff9b::/96"); + lp.addRoute(new RouteInfo(v6, address("fe80::1"), "wlan0", RTN_UNICAST, 1280)); + assertEquals(1, lp.getRoutes().size()); + lp.addRoute(new RouteInfo(v6, address("fe80::1"), "wlan0", RTN_UNICAST, 1500)); + assertEquals(1, lp.getRoutes().size()); + final IpPrefix v4 = new IpPrefix("192.0.2.128/25"); + lp.addRoute(new RouteInfo(v4, address("192.0.2.1"), "wlan0", RTN_UNICAST, 1460)); + assertEquals(2, lp.getRoutes().size()); + lp.addRoute(new RouteInfo(v4, address("192.0.2.1"), "wlan0", RTN_THROW, 1460)); + assertEquals(2, lp.getRoutes().size()); + } } diff --git a/tests/net/common/java/android/net/MatchAllNetworkSpecifierTest.kt b/tests/net/common/java/android/net/MatchAllNetworkSpecifierTest.kt new file mode 100644 index 000000000000..ef15b668e24c --- /dev/null +++ b/tests/net/common/java/android/net/MatchAllNetworkSpecifierTest.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net + +import android.net.wifi.aware.DiscoverySession +import android.net.wifi.aware.PeerHandle +import android.net.wifi.aware.WifiAwareNetworkSpecifier +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 + +import com.android.testutils.assertParcelSane + +import java.lang.IllegalStateException + +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito + +@RunWith(AndroidJUnit4::class) +@SmallTest +class MatchAllNetworkSpecifierTest { + @Test + fun testParcel() { + assertParcelSane(MatchAllNetworkSpecifier(), 0) + } + + @Test(expected = IllegalStateException::class) + fun testSatisfiedBy() { + val specifier = MatchAllNetworkSpecifier() + val discoverySession = Mockito.mock(DiscoverySession::class.java) + val peerHandle = Mockito.mock(PeerHandle::class.java) + val wifiAwareNetworkSpecifier = WifiAwareNetworkSpecifier.Builder(discoverySession, + peerHandle).build() + specifier.satisfiedBy(wifiAwareNetworkSpecifier) + } +} diff --git a/tests/net/common/java/android/net/NattKeepalivePacketDataTest.kt b/tests/net/common/java/android/net/NattKeepalivePacketDataTest.kt new file mode 100644 index 000000000000..46f39dd016fd --- /dev/null +++ b/tests/net/common/java/android/net/NattKeepalivePacketDataTest.kt @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net + +import android.net.InvalidPacketException.ERROR_INVALID_IP_ADDRESS +import android.net.InvalidPacketException.ERROR_INVALID_PORT +import android.net.NattSocketKeepalive.NATT_PORT +import android.os.Build +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.testutils.assertEqualBothWays +import com.android.testutils.assertFieldCountEquals +import com.android.testutils.assertParcelSane +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo +import com.android.testutils.parcelingRoundTrip +import java.net.InetAddress +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotEquals +import org.junit.Assert.fail +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class NattKeepalivePacketDataTest { + @Rule @JvmField + val ignoreRule: DevSdkIgnoreRule = DevSdkIgnoreRule() + + /* Refer to the definition in {@code NattKeepalivePacketData} */ + private val IPV4_HEADER_LENGTH = 20 + private val UDP_HEADER_LENGTH = 8 + + private val TEST_PORT = 4243 + private val TEST_PORT2 = 4244 + private val TEST_SRC_ADDRV4 = "198.168.0.2".address() + private val TEST_DST_ADDRV4 = "198.168.0.1".address() + private val TEST_ADDRV6 = "2001:db8::1".address() + + private fun String.address() = InetAddresses.parseNumericAddress(this) + private fun nattKeepalivePacket( + srcAddress: InetAddress? = TEST_SRC_ADDRV4, + srcPort: Int = TEST_PORT, + dstAddress: InetAddress? = TEST_DST_ADDRV4, + dstPort: Int = NATT_PORT + ) = NattKeepalivePacketData.nattKeepalivePacket(srcAddress, srcPort, dstAddress, dstPort) + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testConstructor() { + try { + nattKeepalivePacket(dstPort = TEST_PORT) + fail("Dst port is not NATT port should cause exception") + } catch (e: InvalidPacketException) { + assertEquals(e.error, ERROR_INVALID_PORT) + } + + try { + nattKeepalivePacket(srcAddress = TEST_ADDRV6) + fail("A v6 srcAddress should cause exception") + } catch (e: InvalidPacketException) { + assertEquals(e.error, ERROR_INVALID_IP_ADDRESS) + } + + try { + nattKeepalivePacket(dstAddress = TEST_ADDRV6) + fail("A v6 dstAddress should cause exception") + } catch (e: InvalidPacketException) { + assertEquals(e.error, ERROR_INVALID_IP_ADDRESS) + } + + try { + parcelingRoundTrip( + NattKeepalivePacketData(TEST_SRC_ADDRV4, TEST_PORT, TEST_DST_ADDRV4, TEST_PORT, + byteArrayOf(12, 31, 22, 44))) + fail("Invalid data should cause exception") + } catch (e: IllegalArgumentException) { } + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testParcel() { + assertParcelSane(nattKeepalivePacket(), 0) + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testEquals() { + assertEqualBothWays(nattKeepalivePacket(), nattKeepalivePacket()) + assertNotEquals(nattKeepalivePacket(dstAddress = TEST_SRC_ADDRV4), nattKeepalivePacket()) + assertNotEquals(nattKeepalivePacket(srcAddress = TEST_DST_ADDRV4), nattKeepalivePacket()) + // Test src port only because dst port have to be NATT_PORT + assertNotEquals(nattKeepalivePacket(srcPort = TEST_PORT2), nattKeepalivePacket()) + // Make sure the parceling test is updated if fields are added in the base class. + assertFieldCountEquals(5, KeepalivePacketData::class.java) + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testHashCode() { + assertEquals(nattKeepalivePacket().hashCode(), nattKeepalivePacket().hashCode()) + } +}
\ No newline at end of file diff --git a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt new file mode 100644 index 000000000000..a4d8353d1253 --- /dev/null +++ b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net + +import android.os.Build +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo +import com.android.testutils.assertParcelSane +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class NetworkAgentConfigTest { + @Rule @JvmField + val ignoreRule = DevSdkIgnoreRule() + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testParcelNetworkAgentConfig() { + val config = NetworkAgentConfig.Builder().apply { + setExplicitlySelected(true) + setLegacyType(ConnectivityManager.TYPE_ETHERNET) + setSubscriberId("MySubId") + setPartialConnectivityAcceptable(false) + setUnvalidatedConnectivityAcceptable(true) + }.build() + assertParcelSane(config, 10) + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testBuilder() { + val config = NetworkAgentConfig.Builder().apply { + setExplicitlySelected(true) + setLegacyType(ConnectivityManager.TYPE_ETHERNET) + setSubscriberId("MySubId") + setPartialConnectivityAcceptable(false) + setUnvalidatedConnectivityAcceptable(true) + setLegacyTypeName("TEST_NETWORK") + disableNat64Detection() + disableProvisioningNotification() + }.build() + + assertTrue(config.isExplicitlySelected()) + assertEquals(ConnectivityManager.TYPE_ETHERNET, config.getLegacyType()) + assertEquals("MySubId", config.getSubscriberId()) + assertFalse(config.isPartialConnectivityAcceptable()) + assertTrue(config.isUnvalidatedConnectivityAcceptable()) + assertEquals("TEST_NETWORK", config.getLegacyTypeName()) + assertFalse(config.isNat64DetectionEnabled()) + assertFalse(config.isProvisioningNotificationEnabled()) + } +} diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java index 6bc7c1bb30f8..3f8261d5ad7f 100644 --- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java @@ -17,6 +17,8 @@ package android.net; import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; +import static android.net.NetworkCapabilities.MAX_TRANSPORT; +import static android.net.NetworkCapabilities.MIN_TRANSPORT; import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS; @@ -32,28 +34,45 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVIT import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P; import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES; +import static android.net.NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_TEST; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES; +import static com.android.testutils.ParcelUtilsKt.assertParcelSane; +import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless; + import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import android.os.Parcel; +import android.net.wifi.aware.DiscoverySession; +import android.net.wifi.aware.PeerHandle; +import android.net.wifi.aware.WifiAwareNetworkSpecifier; +import android.os.Build; +import android.os.Process; import android.test.suitebuilder.annotation.SmallTest; import android.util.ArraySet; +import androidx.core.os.BuildCompat; import androidx.test.runner.AndroidJUnit4; +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; + +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mockito; +import java.util.Arrays; import java.util.Set; @RunWith(AndroidJUnit4.class) @@ -62,6 +81,19 @@ public class NetworkCapabilitiesTest { private static final String TEST_SSID = "TEST_SSID"; private static final String DIFFERENT_TEST_SSID = "DIFFERENT_TEST_SSID"; + @Rule + public DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule(); + + private DiscoverySession mDiscoverySession = Mockito.mock(DiscoverySession.class); + private PeerHandle mPeerHandle = Mockito.mock(PeerHandle.class); + + private boolean isAtLeastR() { + // BuildCompat.isAtLeastR() is used to check the Android version before releasing Android R. + // Build.VERSION.SDK_INT > Build.VERSION_CODES.Q is used to check the Android version after + // releasing Android R. + return BuildCompat.isAtLeastR() || Build.VERSION.SDK_INT > Build.VERSION_CODES.Q; + } + @Test public void testMaybeMarkCapabilitiesRestricted() { // verify EIMS is restricted @@ -267,9 +299,36 @@ public class NetworkCapabilitiesTest { .setUids(uids) .addCapability(NET_CAPABILITY_EIMS) .addCapability(NET_CAPABILITY_NOT_METERED); - assertEqualsThroughMarshalling(netCap); + if (isAtLeastR()) { + netCap.setOwnerUid(123); + netCap.setAdministratorUids(new int[] {5, 11}); + } + assertParcelingIsLossless(netCap); netCap.setSSID(TEST_SSID); - assertEqualsThroughMarshalling(netCap); + testParcelSane(netCap); + } + + @Test + public void testParcelNetworkCapabilitiesWithRequestorUidAndPackageName() { + final NetworkCapabilities netCap = new NetworkCapabilities() + .addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_EIMS) + .addCapability(NET_CAPABILITY_NOT_METERED); + if (isAtLeastR()) { + netCap.setRequestorPackageName("com.android.test"); + netCap.setRequestorUid(9304); + } + assertParcelingIsLossless(netCap); + netCap.setSSID(TEST_SSID); + testParcelSane(netCap); + } + + private void testParcelSane(NetworkCapabilities cap) { + if (isAtLeastR()) { + assertParcelSane(cap, 15); + } else { + assertParcelSane(cap, 11); + } } @Test @@ -402,6 +461,23 @@ public class NetworkCapabilitiesTest { return range; } + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testSetAdministratorUids() { + NetworkCapabilities nc = + new NetworkCapabilities().setAdministratorUids(new int[] {2, 1, 3}); + + assertArrayEquals(new int[] {1, 2, 3}, nc.getAdministratorUids()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testSetAdministratorUidsWithDuplicates() { + try { + new NetworkCapabilities().setAdministratorUids(new int[] {1, 1}); + fail("Expected IllegalArgumentException for duplicate uids"); + } catch (IllegalArgumentException expected) { + } + } + @Test public void testCombineCapabilities() { NetworkCapabilities nc1 = new NetworkCapabilities(); @@ -426,7 +502,9 @@ public class NetworkCapabilitiesTest { nc1.setSSID(TEST_SSID); nc2.combineCapabilities(nc1); - assertTrue(TEST_SSID.equals(nc2.getSSID())); + if (isAtLeastR()) { + assertTrue(TEST_SSID.equals(nc2.getSsid())); + } // Because they now have the same SSID, the following call should not throw nc2.combineCapabilities(nc1); @@ -454,6 +532,31 @@ public class NetworkCapabilitiesTest { assertTrue(nc2.appliesToUid(22)); } + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testCombineCapabilities_AdministratorUids() { + final NetworkCapabilities nc1 = new NetworkCapabilities(); + final NetworkCapabilities nc2 = new NetworkCapabilities(); + + final int[] adminUids = {3, 6, 12}; + nc1.setAdministratorUids(adminUids); + nc2.combineCapabilities(nc1); + assertTrue(nc2.equalsAdministratorUids(nc1)); + assertArrayEquals(nc2.getAdministratorUids(), adminUids); + + final int[] adminUidsOtherOrder = {3, 12, 6}; + nc1.setAdministratorUids(adminUidsOtherOrder); + assertTrue(nc2.equalsAdministratorUids(nc1)); + + final int[] adminUids2 = {11, 1, 12, 3, 6}; + nc1.setAdministratorUids(adminUids2); + assertFalse(nc2.equalsAdministratorUids(nc1)); + assertFalse(Arrays.equals(nc2.getAdministratorUids(), adminUids2)); + try { + nc2.combineCapabilities(nc1); + fail("Shouldn't be able to combine different lists of admin UIDs"); + } catch (IllegalStateException expected) { } + } + @Test public void testSetCapabilities() { final int[] REQUIRED_CAPABILITIES = new int[] { @@ -542,18 +645,6 @@ public class NetworkCapabilitiesTest { nc1.combineCapabilities(nc3); } - private void assertEqualsThroughMarshalling(NetworkCapabilities netCap) { - Parcel p = Parcel.obtain(); - netCap.writeToParcel(p, /* flags */ 0); - p.setDataPosition(0); - byte[] marshalledData = p.marshall(); - - p = Parcel.obtain(); - p.unmarshall(marshalledData, 0, marshalledData.length); - p.setDataPosition(0); - assertEquals(NetworkCapabilities.CREATOR.createFromParcel(p), netCap); - } - @Test public void testSet() { NetworkCapabilities nc1 = new NetworkCapabilities(); @@ -576,12 +667,16 @@ public class NetworkCapabilitiesTest { // from nc2. assertFalse(nc2.hasCapability(NET_CAPABILITY_NOT_ROAMING)); assertTrue(nc2.hasUnwantedCapability(NET_CAPABILITY_NOT_ROAMING)); - assertTrue(TEST_SSID.equals(nc2.getSSID())); + if (isAtLeastR()) { + assertTrue(TEST_SSID.equals(nc2.getSsid())); + } nc1.setSSID(DIFFERENT_TEST_SSID); nc2.set(nc1); assertEquals(nc1, nc2); - assertTrue(DIFFERENT_TEST_SSID.equals(nc2.getSSID())); + if (isAtLeastR()) { + assertTrue(DIFFERENT_TEST_SSID.equals(nc2.getSsid())); + } nc1.setUids(uidRange(10, 13)); nc2.set(nc1); // Overwrites, as opposed to combineCapabilities @@ -603,4 +698,238 @@ public class NetworkCapabilitiesTest { assertEquals(TRANSPORT_VPN, transportTypes[2]); assertEquals(TRANSPORT_TEST, transportTypes[3]); } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testTelephonyNetworkSpecifier() { + final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1); + final NetworkCapabilities nc1 = new NetworkCapabilities.Builder() + .addTransportType(TRANSPORT_WIFI) + .setNetworkSpecifier(specifier) + .build(); + assertEquals(specifier, nc1.getNetworkSpecifier()); + try { + final NetworkCapabilities nc2 = new NetworkCapabilities.Builder() + .setNetworkSpecifier(specifier) + .build(); + fail("Must have a single transport type. Without transport type or multiple transport" + + " types is invalid."); + } catch (IllegalStateException expected) { } + } + + @Test + public void testWifiAwareNetworkSpecifier() { + final NetworkCapabilities nc = new NetworkCapabilities() + .addTransportType(TRANSPORT_WIFI_AWARE); + // If NetworkSpecifier is not set, the default value is null. + assertNull(nc.getNetworkSpecifier()); + final WifiAwareNetworkSpecifier specifier = new WifiAwareNetworkSpecifier.Builder( + mDiscoverySession, mPeerHandle).build(); + nc.setNetworkSpecifier(specifier); + assertEquals(specifier, nc.getNetworkSpecifier()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testAdministratorUidsAndOwnerUid() { + // Test default owner uid. + // If the owner uid is not set, the default value should be Process.INVALID_UID. + final NetworkCapabilities nc1 = new NetworkCapabilities.Builder().build(); + assertEquals(Process.INVALID_UID, nc1.getOwnerUid()); + // Test setAdministratorUids and getAdministratorUids. + final int[] administratorUids = {1001, 10001}; + final NetworkCapabilities nc2 = new NetworkCapabilities.Builder() + .setAdministratorUids(administratorUids) + .build(); + assertTrue(Arrays.equals(administratorUids, nc2.getAdministratorUids())); + // Test setOwnerUid and getOwnerUid. + // The owner UID must be included in administrator UIDs, or throw IllegalStateException. + try { + final NetworkCapabilities nc3 = new NetworkCapabilities.Builder() + .setOwnerUid(1001) + .build(); + fail("The owner UID must be included in administrator UIDs."); + } catch (IllegalStateException expected) { } + final NetworkCapabilities nc4 = new NetworkCapabilities.Builder() + .setAdministratorUids(administratorUids) + .setOwnerUid(1001) + .build(); + assertEquals(1001, nc4.getOwnerUid()); + try { + final NetworkCapabilities nc5 = new NetworkCapabilities.Builder() + .setAdministratorUids(null) + .build(); + fail("Should not set null into setAdministratorUids"); + } catch (NullPointerException expected) { } + } + + @Test + public void testLinkBandwidthKbps() { + final NetworkCapabilities nc = new NetworkCapabilities(); + // The default value of LinkDown/UpstreamBandwidthKbps should be LINK_BANDWIDTH_UNSPECIFIED. + assertEquals(LINK_BANDWIDTH_UNSPECIFIED, nc.getLinkDownstreamBandwidthKbps()); + assertEquals(LINK_BANDWIDTH_UNSPECIFIED, nc.getLinkUpstreamBandwidthKbps()); + nc.setLinkDownstreamBandwidthKbps(512); + nc.setLinkUpstreamBandwidthKbps(128); + assertEquals(512, nc.getLinkDownstreamBandwidthKbps()); + assertNotEquals(128, nc.getLinkDownstreamBandwidthKbps()); + assertEquals(128, nc.getLinkUpstreamBandwidthKbps()); + assertNotEquals(512, nc.getLinkUpstreamBandwidthKbps()); + } + + @Test + public void testSignalStrength() { + final NetworkCapabilities nc = new NetworkCapabilities(); + // The default value of signal strength should be SIGNAL_STRENGTH_UNSPECIFIED. + assertEquals(SIGNAL_STRENGTH_UNSPECIFIED, nc.getSignalStrength()); + nc.setSignalStrength(-80); + assertEquals(-80, nc.getSignalStrength()); + assertNotEquals(-50, nc.getSignalStrength()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testDeduceRestrictedCapability() { + final NetworkCapabilities nc = new NetworkCapabilities(); + // Default capabilities don't have restricted capability. + assertFalse(nc.deduceRestrictedCapability()); + // If there is a force restricted capability, then the network capabilities is restricted. + nc.addCapability(NET_CAPABILITY_OEM_PAID); + nc.addCapability(NET_CAPABILITY_INTERNET); + assertTrue(nc.deduceRestrictedCapability()); + // Except for the force restricted capability, if there is any unrestricted capability in + // capabilities, then the network capabilities is not restricted. + nc.removeCapability(NET_CAPABILITY_OEM_PAID); + nc.addCapability(NET_CAPABILITY_CBS); + assertFalse(nc.deduceRestrictedCapability()); + // Except for the force restricted capability, the network capabilities will only be treated + // as restricted when there is no any unrestricted capability. + nc.removeCapability(NET_CAPABILITY_INTERNET); + assertTrue(nc.deduceRestrictedCapability()); + } + + private void assertNoTransport(NetworkCapabilities nc) { + for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) { + assertFalse(nc.hasTransport(i)); + } + } + + // Checks that all transport types from MIN_TRANSPORT to maxTransportType are set and all + // transport types from maxTransportType + 1 to MAX_TRANSPORT are not set when positiveSequence + // is true. If positiveSequence is false, then the check sequence is opposite. + private void checkCurrentTransportTypes(NetworkCapabilities nc, int maxTransportType, + boolean positiveSequence) { + for (int i = MIN_TRANSPORT; i <= maxTransportType; i++) { + if (positiveSequence) { + assertTrue(nc.hasTransport(i)); + } else { + assertFalse(nc.hasTransport(i)); + } + } + for (int i = MAX_TRANSPORT; i > maxTransportType; i--) { + if (positiveSequence) { + assertFalse(nc.hasTransport(i)); + } else { + assertTrue(nc.hasTransport(i)); + } + } + } + + @Test + public void testMultipleTransportTypes() { + final NetworkCapabilities nc = new NetworkCapabilities(); + assertNoTransport(nc); + // Test adding multiple transport types. + for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) { + nc.addTransportType(i); + checkCurrentTransportTypes(nc, i, true /* positiveSequence */); + } + // Test removing multiple transport types. + for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) { + nc.removeTransportType(i); + checkCurrentTransportTypes(nc, i, false /* positiveSequence */); + } + assertNoTransport(nc); + nc.addTransportType(TRANSPORT_WIFI); + assertTrue(nc.hasTransport(TRANSPORT_WIFI)); + assertFalse(nc.hasTransport(TRANSPORT_VPN)); + nc.addTransportType(TRANSPORT_VPN); + assertTrue(nc.hasTransport(TRANSPORT_WIFI)); + assertTrue(nc.hasTransport(TRANSPORT_VPN)); + nc.removeTransportType(TRANSPORT_WIFI); + assertFalse(nc.hasTransport(TRANSPORT_WIFI)); + assertTrue(nc.hasTransport(TRANSPORT_VPN)); + nc.removeTransportType(TRANSPORT_VPN); + assertFalse(nc.hasTransport(TRANSPORT_WIFI)); + assertFalse(nc.hasTransport(TRANSPORT_VPN)); + assertNoTransport(nc); + } + + @Test + public void testAddAndRemoveTransportType() { + final NetworkCapabilities nc = new NetworkCapabilities(); + try { + nc.addTransportType(-1); + fail("Should not set invalid transport type into addTransportType"); + } catch (IllegalArgumentException expected) { } + try { + nc.removeTransportType(-1); + fail("Should not set invalid transport type into removeTransportType"); + } catch (IllegalArgumentException e) { } + } + + private class TestTransportInfo implements TransportInfo { + TestTransportInfo() { + } + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testBuilder() { + final int ownerUid = 1001; + final int signalStrength = -80; + final int requestUid = 10100; + final int[] administratorUids = {ownerUid, 10001}; + final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1); + final TestTransportInfo transportInfo = new TestTransportInfo(); + final String ssid = "TEST_SSID"; + final String packageName = "com.google.test.networkcapabilities"; + final NetworkCapabilities nc = new NetworkCapabilities.Builder() + .addTransportType(TRANSPORT_WIFI) + .addTransportType(TRANSPORT_CELLULAR) + .removeTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_EIMS) + .addCapability(NET_CAPABILITY_CBS) + .removeCapability(NET_CAPABILITY_CBS) + .setAdministratorUids(administratorUids) + .setOwnerUid(ownerUid) + .setLinkDownstreamBandwidthKbps(512) + .setLinkUpstreamBandwidthKbps(128) + .setNetworkSpecifier(specifier) + .setTransportInfo(transportInfo) + .setSignalStrength(signalStrength) + .setSsid(ssid) + .setRequestorUid(requestUid) + .setRequestorPackageName(packageName) + .build(); + assertEquals(1, nc.getTransportTypes().length); + assertEquals(TRANSPORT_WIFI, nc.getTransportTypes()[0]); + assertTrue(nc.hasCapability(NET_CAPABILITY_EIMS)); + assertFalse(nc.hasCapability(NET_CAPABILITY_CBS)); + assertTrue(Arrays.equals(administratorUids, nc.getAdministratorUids())); + assertEquals(ownerUid, nc.getOwnerUid()); + assertEquals(512, nc.getLinkDownstreamBandwidthKbps()); + assertNotEquals(128, nc.getLinkDownstreamBandwidthKbps()); + assertEquals(128, nc.getLinkUpstreamBandwidthKbps()); + assertNotEquals(512, nc.getLinkUpstreamBandwidthKbps()); + assertEquals(specifier, nc.getNetworkSpecifier()); + assertEquals(transportInfo, nc.getTransportInfo()); + assertEquals(signalStrength, nc.getSignalStrength()); + assertNotEquals(-50, nc.getSignalStrength()); + assertEquals(ssid, nc.getSsid()); + assertEquals(requestUid, nc.getRequestorUid()); + assertEquals(packageName, nc.getRequestorPackageName()); + // Cannot assign null into NetworkCapabilities.Builder + try { + final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(null); + fail("Should not set null into NetworkCapabilities.Builder"); + } catch (NullPointerException expected) { } + assertEquals(nc, new NetworkCapabilities.Builder(nc).build()); + } } diff --git a/tests/net/common/java/android/net/NetworkProviderTest.kt b/tests/net/common/java/android/net/NetworkProviderTest.kt new file mode 100644 index 000000000000..b7c47c2bc223 --- /dev/null +++ b/tests/net/common/java/android/net/NetworkProviderTest.kt @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net + +import android.app.Instrumentation +import android.content.Context +import android.net.NetworkCapabilities.TRANSPORT_TEST +import android.os.Build +import android.os.HandlerThread +import android.os.Looper +import android.net.NetworkProviderTest.TestNetworkCallback.CallbackEntry.OnUnavailable +import android.net.NetworkProviderTest.TestNetworkProvider.CallbackEntry.OnNetworkRequested +import android.net.NetworkProviderTest.TestNetworkProvider.CallbackEntry.OnNetworkRequestWithdrawn +import androidx.test.InstrumentationRegistry +import com.android.testutils.ArrayTrackRecord +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo +import com.android.testutils.DevSdkIgnoreRunner +import java.util.UUID +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +private const val DEFAULT_TIMEOUT_MS = 5000L +private val instrumentation: Instrumentation + get() = InstrumentationRegistry.getInstrumentation() +private val context: Context get() = InstrumentationRegistry.getContext() +private val PROVIDER_NAME = "NetworkProviderTest" + +@RunWith(DevSdkIgnoreRunner::class) +@IgnoreUpTo(Build.VERSION_CODES.Q) +class NetworkProviderTest { + private val mCm = context.getSystemService(ConnectivityManager::class.java) + private val mHandlerThread = HandlerThread("${javaClass.simpleName} handler thread") + + @Before + fun setUp() { + instrumentation.getUiAutomation().adoptShellPermissionIdentity() + mHandlerThread.start() + } + + @After + fun tearDown() { + mHandlerThread.quitSafely() + instrumentation.getUiAutomation().dropShellPermissionIdentity() + } + + private class TestNetworkProvider(context: Context, looper: Looper) : + NetworkProvider(context, looper, PROVIDER_NAME) { + private val seenEvents = ArrayTrackRecord<CallbackEntry>().newReadHead() + + sealed class CallbackEntry { + data class OnNetworkRequested( + val request: NetworkRequest, + val score: Int, + val id: Int + ) : CallbackEntry() + data class OnNetworkRequestWithdrawn(val request: NetworkRequest) : CallbackEntry() + } + + override fun onNetworkRequested(request: NetworkRequest, score: Int, id: Int) { + seenEvents.add(OnNetworkRequested(request, score, id)) + } + + override fun onNetworkRequestWithdrawn(request: NetworkRequest) { + seenEvents.add(OnNetworkRequestWithdrawn(request)) + } + + inline fun <reified T : CallbackEntry> expectCallback( + crossinline predicate: (T) -> Boolean + ) = seenEvents.poll(DEFAULT_TIMEOUT_MS) { it is T && predicate(it) } + } + + private fun createNetworkProvider(): TestNetworkProvider { + return TestNetworkProvider(context, mHandlerThread.looper) + } + + @Test + fun testOnNetworkRequested() { + val provider = createNetworkProvider() + assertEquals(provider.getProviderId(), NetworkProvider.ID_NONE) + mCm.registerNetworkProvider(provider) + assertNotEquals(provider.getProviderId(), NetworkProvider.ID_NONE) + + val specifier = StringNetworkSpecifier(UUID.randomUUID().toString()) + val nr: NetworkRequest = NetworkRequest.Builder() + .addTransportType(TRANSPORT_TEST) + .setNetworkSpecifier(specifier) + .build() + val cb = ConnectivityManager.NetworkCallback() + mCm.requestNetwork(nr, cb) + provider.expectCallback<OnNetworkRequested>() { callback -> + callback.request.getNetworkSpecifier() == specifier && + callback.request.hasTransport(TRANSPORT_TEST) + } + + val initialScore = 40 + val updatedScore = 60 + val nc = NetworkCapabilities().apply { + addTransportType(NetworkCapabilities.TRANSPORT_TEST) + removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) + removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED) + addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING) + addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) + setNetworkSpecifier(specifier) + } + val lp = LinkProperties() + val config = NetworkAgentConfig.Builder().build() + val agent = object : NetworkAgent(context, mHandlerThread.looper, "TestAgent", nc, lp, + initialScore, config, provider) {} + + provider.expectCallback<OnNetworkRequested>() { callback -> + callback.request.getNetworkSpecifier() == specifier && + callback.score == initialScore && + callback.id == agent.providerId + } + + agent.sendNetworkScore(updatedScore) + provider.expectCallback<OnNetworkRequested>() { callback -> + callback.request.getNetworkSpecifier() == specifier && + callback.score == updatedScore && + callback.id == agent.providerId + } + + mCm.unregisterNetworkCallback(cb) + provider.expectCallback<OnNetworkRequestWithdrawn>() { callback -> + callback.request.getNetworkSpecifier() == specifier && + callback.request.hasTransport(TRANSPORT_TEST) + } + mCm.unregisterNetworkProvider(provider) + // Provider id should be ID_NONE after unregister network provider + assertEquals(provider.getProviderId(), NetworkProvider.ID_NONE) + // unregisterNetworkProvider should not crash even if it's called on an + // already unregistered provider. + mCm.unregisterNetworkProvider(provider) + } + + private class TestNetworkCallback : ConnectivityManager.NetworkCallback() { + private val seenEvents = ArrayTrackRecord<CallbackEntry>().newReadHead() + sealed class CallbackEntry { + object OnUnavailable : CallbackEntry() + } + + override fun onUnavailable() { + seenEvents.add(OnUnavailable) + } + + inline fun <reified T : CallbackEntry> expectCallback( + crossinline predicate: (T) -> Boolean + ) = seenEvents.poll(DEFAULT_TIMEOUT_MS) { it is T && predicate(it) } + } + + @Test + fun testDeclareNetworkRequestUnfulfillable() { + val provider = createNetworkProvider() + mCm.registerNetworkProvider(provider) + + val specifier = StringNetworkSpecifier(UUID.randomUUID().toString()) + val nr: NetworkRequest = NetworkRequest.Builder() + .addTransportType(TRANSPORT_TEST) + .setNetworkSpecifier(specifier) + .build() + + val cb = TestNetworkCallback() + mCm.requestNetwork(nr, cb) + provider.declareNetworkRequestUnfulfillable(nr) + cb.expectCallback<OnUnavailable>() { nr.getNetworkSpecifier() == specifier } + mCm.unregisterNetworkProvider(provider) + } +}
\ No newline at end of file diff --git a/tests/net/common/java/android/net/NetworkSpecifierTest.kt b/tests/net/common/java/android/net/NetworkSpecifierTest.kt new file mode 100644 index 000000000000..f3409f53596f --- /dev/null +++ b/tests/net/common/java/android/net/NetworkSpecifierTest.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net + +import android.os.Build +import androidx.test.filters.SmallTest +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo +import com.android.testutils.DevSdkIgnoreRunner +import kotlin.test.assertTrue +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotEquals +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(DevSdkIgnoreRunner::class) +@IgnoreUpTo(Build.VERSION_CODES.Q) +class NetworkSpecifierTest { + private class TestNetworkSpecifier( + val intData: Int = 123, + val stringData: String = "init" + ) : NetworkSpecifier() { + override fun canBeSatisfiedBy(other: NetworkSpecifier?): Boolean = + other != null && + other is TestNetworkSpecifier && + other.intData >= intData && + stringData.equals(other.stringData) + + override fun redact(): NetworkSpecifier = TestNetworkSpecifier(intData, "redact") + } + + @Test + fun testRedact() { + val ns: TestNetworkSpecifier = TestNetworkSpecifier() + val redactNs = ns.redact() + assertTrue(redactNs is TestNetworkSpecifier) + assertEquals(ns.intData, redactNs.intData) + assertNotEquals(ns.stringData, redactNs.stringData) + assertTrue("redact".equals(redactNs.stringData)) + } + + @Test + fun testcanBeSatisfiedBy() { + val target: TestNetworkSpecifier = TestNetworkSpecifier() + assertFalse(target.canBeSatisfiedBy(null)) + assertTrue(target.canBeSatisfiedBy(TestNetworkSpecifier())) + val otherNs = TelephonyNetworkSpecifier.Builder().setSubscriptionId(123).build() + assertFalse(target.canBeSatisfiedBy(otherNs)) + assertTrue(target.canBeSatisfiedBy(TestNetworkSpecifier(intData = 999))) + assertFalse(target.canBeSatisfiedBy(TestNetworkSpecifier(intData = 1))) + assertFalse(target.canBeSatisfiedBy(TestNetworkSpecifier(stringData = "diff"))) + } +}
\ No newline at end of file diff --git a/tests/net/java/android/net/NetworkStackTest.java b/tests/net/common/java/android/net/NetworkStackTest.java index f7c6c99ba622..a99aa0106655 100644 --- a/tests/net/java/android/net/NetworkStackTest.java +++ b/tests/net/common/java/android/net/NetworkStackTest.java @@ -22,16 +22,23 @@ import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.net.NetworkStack.checkNetworkStackPermission; import static android.net.NetworkStack.checkNetworkStackPermissionOr; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.when; import android.content.Context; +import android.os.Build; +import android.os.IBinder; import androidx.test.runner.AndroidJUnit4; +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; + import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -41,7 +48,11 @@ import org.mockito.MockitoAnnotations; public class NetworkStackTest { private static final String [] OTHER_PERMISSION = {"otherpermission1", "otherpermission2"}; + @Rule + public DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule(); + @Mock Context mCtx; + @Mock private IBinder mConnectorBinder; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -72,4 +83,10 @@ public class NetworkStackTest { fail("Expect fail but permission granted."); } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testGetService() { + NetworkStack.setServiceForTest(mConnectorBinder); + assertEquals(NetworkStack.getService(), mConnectorBinder); + } } diff --git a/tests/net/common/java/android/net/NetworkTest.java b/tests/net/common/java/android/net/NetworkTest.java index 38bc744a0a06..11d44b86bc50 100644 --- a/tests/net/common/java/android/net/NetworkTest.java +++ b/tests/net/common/java/android/net/NetworkTest.java @@ -18,13 +18,10 @@ package android.net; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import android.net.LocalServerSocket; -import android.net.LocalSocket; -import android.net.LocalSocketAddress; -import android.net.Network; import android.platform.test.annotations.AppModeFull; import androidx.test.filters.SmallTest; @@ -40,7 +37,6 @@ import java.net.DatagramSocket; import java.net.Inet6Address; import java.net.InetAddress; import java.net.SocketException; -import java.util.Objects; @RunWith(AndroidJUnit4.class) @SmallTest @@ -123,29 +119,29 @@ public class NetworkTest { Network three = new Network(3); // None of the hashcodes are zero. - assertNotEqual(0, one.hashCode()); - assertNotEqual(0, two.hashCode()); - assertNotEqual(0, three.hashCode()); + assertNotEquals(0, one.hashCode()); + assertNotEquals(0, two.hashCode()); + assertNotEquals(0, three.hashCode()); // All the hashcodes are distinct. - assertNotEqual(one.hashCode(), two.hashCode()); - assertNotEqual(one.hashCode(), three.hashCode()); - assertNotEqual(two.hashCode(), three.hashCode()); + assertNotEquals(one.hashCode(), two.hashCode()); + assertNotEquals(one.hashCode(), three.hashCode()); + assertNotEquals(two.hashCode(), three.hashCode()); // None of the handles are zero. - assertNotEqual(0, one.getNetworkHandle()); - assertNotEqual(0, two.getNetworkHandle()); - assertNotEqual(0, three.getNetworkHandle()); + assertNotEquals(0, one.getNetworkHandle()); + assertNotEquals(0, two.getNetworkHandle()); + assertNotEquals(0, three.getNetworkHandle()); // All the handles are distinct. - assertNotEqual(one.getNetworkHandle(), two.getNetworkHandle()); - assertNotEqual(one.getNetworkHandle(), three.getNetworkHandle()); - assertNotEqual(two.getNetworkHandle(), three.getNetworkHandle()); + assertNotEquals(one.getNetworkHandle(), two.getNetworkHandle()); + assertNotEquals(one.getNetworkHandle(), three.getNetworkHandle()); + assertNotEquals(two.getNetworkHandle(), three.getNetworkHandle()); // The handles are not equal to the hashcodes. - assertNotEqual(one.hashCode(), one.getNetworkHandle()); - assertNotEqual(two.hashCode(), two.getNetworkHandle()); - assertNotEqual(three.hashCode(), three.getNetworkHandle()); + assertNotEquals(one.hashCode(), one.getNetworkHandle()); + assertNotEquals(two.hashCode(), two.getNetworkHandle()); + assertNotEquals(three.hashCode(), three.getNetworkHandle()); // Adjust as necessary to test an implementation's specific constants. // When running with runtest, "adb logcat -s TestRunner" can be useful. @@ -154,15 +150,11 @@ public class NetworkTest { assertEquals(16290598925L, three.getNetworkHandle()); } - private static <T> void assertNotEqual(T t1, T t2) { - assertFalse(Objects.equals(t1, t2)); - } - @Test public void testGetPrivateDnsBypassingCopy() { final Network copy = mNetwork.getPrivateDnsBypassingCopy(); assertEquals(mNetwork.netId, copy.netId); - assertNotEqual(copy.netId, copy.getNetIdForResolv()); - assertNotEqual(mNetwork.getNetIdForResolv(), copy.getNetIdForResolv()); + assertNotEquals(copy.netId, copy.getNetIdForResolv()); + assertNotEquals(mNetwork.getNetIdForResolv(), copy.getNetIdForResolv()); } } diff --git a/tests/net/common/java/android/net/RouteInfoTest.java b/tests/net/common/java/android/net/RouteInfoTest.java index 2edbd403b5b5..60cac0b6b0f5 100644 --- a/tests/net/common/java/android/net/RouteInfoTest.java +++ b/tests/net/common/java/android/net/RouteInfoTest.java @@ -18,34 +18,83 @@ package android.net; import static android.net.RouteInfo.RTN_UNREACHABLE; -import android.os.Parcel; -import android.test.suitebuilder.annotation.SmallTest; +import static com.android.testutils.MiscAssertsKt.assertEqualBothWays; +import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals; +import static com.android.testutils.MiscAssertsKt.assertNotEqualEitherWay; +import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless; -import junit.framework.TestCase; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.os.Build; + +import androidx.core.os.BuildCompat; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; -public class RouteInfoTest extends TestCase { +@RunWith(AndroidJUnit4.class) +@SmallTest +public class RouteInfoTest { + @Rule + public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(); + + private static final int INVALID_ROUTE_TYPE = -1; private InetAddress Address(String addr) { - return InetAddress.parseNumericAddress(addr); + return InetAddresses.parseNumericAddress(addr); } private IpPrefix Prefix(String prefix) { return new IpPrefix(prefix); } - @SmallTest + private static boolean isAtLeastR() { + // BuildCompat.isAtLeastR is documented to return false on release SDKs (including R) + return Build.VERSION.SDK_INT > Build.VERSION_CODES.Q || BuildCompat.isAtLeastR(); + } + + @Test public void testConstructor() { RouteInfo r; - // Invalid input. try { r = new RouteInfo((IpPrefix) null, null, "rmnet0"); fail("Expected RuntimeException: destination and gateway null"); - } catch(RuntimeException e) {} + } catch (RuntimeException e) { } + + try { + r = new RouteInfo(Prefix("2001:db8:ace::/49"), Address("2001:db8::1"), "rmnet0", + INVALID_ROUTE_TYPE); + fail("Invalid route type should cause exception"); + } catch (IllegalArgumentException e) { } + + try { + r = new RouteInfo(Prefix("2001:db8:ace::/49"), Address("192.0.2.1"), "rmnet0", + RTN_UNREACHABLE); + fail("Address family mismatch should cause exception"); + } catch (IllegalArgumentException e) { } + + try { + r = new RouteInfo(Prefix("0.0.0.0/0"), Address("2001:db8::1"), "rmnet0", + RTN_UNREACHABLE); + fail("Address family mismatch should cause exception"); + } catch (IllegalArgumentException e) { } // Null destination is default route. r = new RouteInfo((IpPrefix) null, Address("2001:db8::1"), null); @@ -70,6 +119,7 @@ public class RouteInfoTest extends TestCase { assertNull(r.getInterface()); } + @Test public void testMatches() { class PatchedRouteInfo { private final RouteInfo mRouteInfo; @@ -109,49 +159,41 @@ public class RouteInfoTest extends TestCase { assertFalse(ipv4Default.matches(Address("2001:db8::f00"))); } - private void assertAreEqual(Object o1, Object o2) { - assertTrue(o1.equals(o2)); - assertTrue(o2.equals(o1)); - } - - private void assertAreNotEqual(Object o1, Object o2) { - assertFalse(o1.equals(o2)); - assertFalse(o2.equals(o1)); - } - + @Test public void testEquals() { // IPv4 RouteInfo r1 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::1"), "wlan0"); RouteInfo r2 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::1"), "wlan0"); - assertAreEqual(r1, r2); + assertEqualBothWays(r1, r2); RouteInfo r3 = new RouteInfo(Prefix("2001:db8:ace::/49"), Address("2001:db8::1"), "wlan0"); RouteInfo r4 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::2"), "wlan0"); RouteInfo r5 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::1"), "rmnet0"); - assertAreNotEqual(r1, r3); - assertAreNotEqual(r1, r4); - assertAreNotEqual(r1, r5); + assertNotEqualEitherWay(r1, r3); + assertNotEqualEitherWay(r1, r4); + assertNotEqualEitherWay(r1, r5); // IPv6 r1 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), "wlan0"); r2 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), "wlan0"); - assertAreEqual(r1, r2); + assertEqualBothWays(r1, r2); r3 = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), "wlan0"); r4 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.2"), "wlan0"); r5 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), "rmnet0"); - assertAreNotEqual(r1, r3); - assertAreNotEqual(r1, r4); - assertAreNotEqual(r1, r5); + assertNotEqualEitherWay(r1, r3); + assertNotEqualEitherWay(r1, r4); + assertNotEqualEitherWay(r1, r5); // Interfaces (but not destinations or gateways) can be null. r1 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), null); r2 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), null); r3 = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), "wlan0"); - assertAreEqual(r1, r2); - assertAreNotEqual(r1, r3); + assertEqualBothWays(r1, r2); + assertNotEqualEitherWay(r1, r3); } + @Test public void testHostAndDefaultRoutes() { RouteInfo r; @@ -160,80 +202,133 @@ public class RouteInfoTest extends TestCase { assertTrue(r.isDefaultRoute()); assertTrue(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("::/0"), Address("::"), "wlan0"); assertFalse(r.isHostRoute()); assertTrue(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertTrue(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0"); assertFalse(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("2001:db8::/48"), null, "wlan0"); assertFalse(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("192.0.2.0/32"), Address("0.0.0.0"), "wlan0"); assertTrue(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("2001:db8::/128"), Address("::"), "wlan0"); assertTrue(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("192.0.2.0/32"), null, "wlan0"); assertTrue(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("2001:db8::/128"), null, "wlan0"); assertTrue(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("::/128"), Address("fe80::"), "wlan0"); assertTrue(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0"); assertTrue(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0"); assertTrue(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE); assertFalse(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertTrue(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE); assertFalse(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertTrue(r.isIPv6UnreachableDefault()); + } } + @Test public void testTruncation() { LinkAddress l; RouteInfo r; @@ -250,6 +345,7 @@ public class RouteInfoTest extends TestCase { // Make sure that creating routes to multicast addresses doesn't throw an exception. Even though // there's nothing we can do with them, we don't want to crash if, e.g., someone calls // requestRouteToHostAddress("230.0.0.0", MOBILE_HIPRI); + @Test public void testMulticastRoute() { RouteInfo r; r = new RouteInfo(Prefix("230.0.0.0/32"), Address("192.0.2.1"), "wlan0"); @@ -257,32 +353,82 @@ public class RouteInfoTest extends TestCase { // No exceptions? Good. } - public RouteInfo passThroughParcel(RouteInfo r) { - Parcel p = Parcel.obtain(); - RouteInfo r2 = null; - try { - r.writeToParcel(p, 0); - p.setDataPosition(0); - r2 = RouteInfo.CREATOR.createFromParcel(p); - } finally { - p.recycle(); - } - assertNotNull(r2); - return r2; + @Test + public void testParceling() { + RouteInfo r; + r = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), null); + assertParcelingIsLossless(r); + r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0"); + assertParcelingIsLossless(r); + r = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), "wlan0", RTN_UNREACHABLE); + assertParcelingIsLossless(r); } - public void assertParcelingIsLossless(RouteInfo r) { - RouteInfo r2 = passThroughParcel(r); - assertEquals(r, r2); + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testMtuParceling() { + final RouteInfo r = new RouteInfo(Prefix("ff02::1/128"), Address("2001:db8::"), "testiface", + RTN_UNREACHABLE, 1450 /* mtu */); + assertParcelingIsLossless(r); } - public void testParceling() { + @Test @IgnoreAfter(Build.VERSION_CODES.Q) + public void testFieldCount_Q() { + assertFieldCountEquals(6, RouteInfo.class); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testFieldCount() { + // Make sure any new field is covered by the above parceling tests when changing this number + assertFieldCountEquals(7, RouteInfo.class); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testMtu() { RouteInfo r; + r = new RouteInfo(Prefix("0.0.0.0/0"), Address("0.0.0.0"), "wlan0", + RouteInfo.RTN_UNICAST, 1500); + assertEquals(1500, r.getMtu()); - r = new RouteInfo(Prefix("::/0"), Address("2001:db8::"), null); - assertParcelingIsLossless(r); + r = new RouteInfo(Prefix("0.0.0.0/0"), Address("0.0.0.0"), "wlan0"); + assertEquals(0, r.getMtu()); + } - r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0"); - assertParcelingIsLossless(r); + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testRouteKey() { + RouteInfo.RouteKey k1, k2; + // Only prefix, null gateway and null interface + k1 = new RouteInfo(Prefix("2001:db8::/128"), null).getRouteKey(); + k2 = new RouteInfo(Prefix("2001:db8::/128"), null).getRouteKey(); + assertEquals(k1, k2); + assertEquals(k1.hashCode(), k2.hashCode()); + + // With prefix, gateway and interface. Type and MTU does not affect RouteKey equality + k1 = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), "wlan0", + RTN_UNREACHABLE, 1450).getRouteKey(); + k2 = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), "wlan0", + RouteInfo.RTN_UNICAST, 1400).getRouteKey(); + assertEquals(k1, k2); + assertEquals(k1.hashCode(), k2.hashCode()); + + // Different scope IDs are ignored by the kernel, so we consider them equal here too. + k1 = new RouteInfo(Prefix("2001:db8::/64"), Address("fe80::1%1"), "wlan0").getRouteKey(); + k2 = new RouteInfo(Prefix("2001:db8::/64"), Address("fe80::1%2"), "wlan0").getRouteKey(); + assertEquals(k1, k2); + assertEquals(k1.hashCode(), k2.hashCode()); + + // Different prefix + k1 = new RouteInfo(Prefix("192.0.2.0/24"), null).getRouteKey(); + k2 = new RouteInfo(Prefix("192.0.3.0/24"), null).getRouteKey(); + assertNotEquals(k1, k2); + + // Different gateway + k1 = new RouteInfo(Prefix("ff02::1/128"), Address("2001:db8::1"), null).getRouteKey(); + k2 = new RouteInfo(Prefix("ff02::1/128"), Address("2001:db8::2"), null).getRouteKey(); + assertNotEquals(k1, k2); + + // Different interface + k1 = new RouteInfo(Prefix("ff02::1/128"), null, "tun0").getRouteKey(); + k2 = new RouteInfo(Prefix("ff02::1/128"), null, "tun1").getRouteKey(); + assertNotEquals(k1, k2); } } diff --git a/tests/net/common/java/android/net/StaticIpConfigurationTest.java b/tests/net/common/java/android/net/StaticIpConfigurationTest.java index 5096be221cbf..b5f23bf19a3c 100644 --- a/tests/net/common/java/android/net/StaticIpConfigurationTest.java +++ b/tests/net/common/java/android/net/StaticIpConfigurationTest.java @@ -18,6 +18,7 @@ package android.net; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -34,7 +35,6 @@ import java.net.InetAddress; import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import java.util.Objects; @RunWith(AndroidJUnit4.class) @SmallTest @@ -61,10 +61,6 @@ public class StaticIpConfigurationTest { assertEquals(0, s.dnsServers.size()); } - private static <T> void assertNotEquals(T t1, T t2) { - assertFalse(Objects.equals(t1, t2)); - } - private StaticIpConfiguration makeTestObject() { StaticIpConfiguration s = new StaticIpConfiguration(); s.ipAddress = ADDR; diff --git a/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java b/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java index 0ce7c91c04d0..84805442e5c7 100644 --- a/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java +++ b/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java @@ -16,23 +16,36 @@ package android.net.apf; +import static com.android.testutils.ParcelUtilsKt.assertParcelSane; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import android.content.Context; + +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.util.ParcelableTestUtil; -import com.android.internal.util.TestUtils; - +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Arrays; + @RunWith(AndroidJUnit4.class) @SmallTest public class ApfCapabilitiesTest { + private Context mContext; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getContext(); + } + @Test public void testConstructAndParcel() { final ApfCapabilities caps = new ApfCapabilities(123, 456, 789); @@ -40,9 +53,7 @@ public class ApfCapabilitiesTest { assertEquals(456, caps.maximumApfProgramSize); assertEquals(789, caps.apfPacketFormat); - ParcelableTestUtil.assertFieldCountEquals(3, ApfCapabilities.class); - - TestUtils.assertParcelingIsLossless(caps); + assertParcelSane(caps, 3); } @Test @@ -62,4 +73,27 @@ public class ApfCapabilitiesTest { caps = new ApfCapabilities(4 /* apfVersionSupported */, 5, 6); assertTrue(caps.hasDataAccess()); } + + @Test + public void testGetApfDrop8023Frames() { + // Get com.android.internal.R.bool.config_apfDrop802_3Frames. The test cannot directly + // use R.bool.config_apfDrop802_3Frames because that is not a stable resource ID. + final int resId = mContext.getResources().getIdentifier("config_apfDrop802_3Frames", + "bool", "android"); + final boolean shouldDrop8023Frames = mContext.getResources().getBoolean(resId); + final boolean actual = ApfCapabilities.getApfDrop8023Frames(); + assertEquals(shouldDrop8023Frames, actual); + } + + @Test + public void testGetApfEtherTypeBlackList() { + // Get com.android.internal.R.array.config_apfEthTypeBlackList. The test cannot directly + // use R.array.config_apfEthTypeBlackList because that is not a stable resource ID. + final int resId = mContext.getResources().getIdentifier("config_apfEthTypeBlackList", + "array", "android"); + final int[] blacklistedEtherTypeArray = mContext.getResources().getIntArray(resId); + final int[] actual = ApfCapabilities.getApfEtherTypeBlackList(); + assertNotNull(actual); + assertTrue(Arrays.equals(blacklistedEtherTypeArray, actual)); + } } diff --git a/tests/net/common/java/android/net/metrics/ApfProgramEventTest.kt b/tests/net/common/java/android/net/metrics/ApfProgramEventTest.kt index 8d055c93c4c5..0b7b74097cc6 100644 --- a/tests/net/common/java/android/net/metrics/ApfProgramEventTest.kt +++ b/tests/net/common/java/android/net/metrics/ApfProgramEventTest.kt @@ -16,11 +16,9 @@ package android.net.metrics; -import android.os.Parcelable import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 -import com.android.internal.util.ParcelableTestUtil -import com.android.internal.util.TestUtils +import com.android.testutils.assertParcelSane import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -30,11 +28,6 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) @SmallTest class ApfProgramEventTest { - private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) { - ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java) - TestUtils.assertParcelingIsLossless(obj) - } - private infix fun Int.hasFlag(flag: Int) = (this and (1 shl flag)) != 0 @Test @@ -55,7 +48,7 @@ class ApfProgramEventTest { assertEquals(5, apfProgramEvent.programLength) assertEquals(ApfProgramEvent.flagsFor(true, true), apfProgramEvent.flags) - testParcel(apfProgramEvent, 6) + assertParcelSane(apfProgramEvent, 6) } @Test diff --git a/tests/net/common/java/android/net/metrics/ApfStatsTest.kt b/tests/net/common/java/android/net/metrics/ApfStatsTest.kt index f8eb40cccd35..46a8c8e5b509 100644 --- a/tests/net/common/java/android/net/metrics/ApfStatsTest.kt +++ b/tests/net/common/java/android/net/metrics/ApfStatsTest.kt @@ -16,11 +16,9 @@ package android.net.metrics -import android.os.Parcelable import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 -import com.android.internal.util.ParcelableTestUtil -import com.android.internal.util.TestUtils +import com.android.testutils.assertParcelSane import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith @@ -28,11 +26,6 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) @SmallTest class ApfStatsTest { - private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) { - ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java) - TestUtils.assertParcelingIsLossless(obj) - } - @Test fun testBuilderAndParcel() { val apfStats = ApfStats.Builder() @@ -59,6 +52,6 @@ class ApfStatsTest { assertEquals(8, apfStats.programUpdatesAllowingMulticast) assertEquals(9, apfStats.maxProgramSize) - testParcel(apfStats, 10) + assertParcelSane(apfStats, 10) } } diff --git a/tests/net/common/java/android/net/metrics/DhcpClientEventTest.kt b/tests/net/common/java/android/net/metrics/DhcpClientEventTest.kt index 36e9f8c94f6a..8d7a9c405024 100644 --- a/tests/net/common/java/android/net/metrics/DhcpClientEventTest.kt +++ b/tests/net/common/java/android/net/metrics/DhcpClientEventTest.kt @@ -16,11 +16,9 @@ package android.net.metrics -import android.os.Parcelable import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 -import com.android.internal.util.ParcelableTestUtil -import com.android.internal.util.TestUtils +import com.android.testutils.assertParcelSane import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith @@ -30,11 +28,6 @@ private const val FAKE_MESSAGE = "test" @RunWith(AndroidJUnit4::class) @SmallTest class DhcpClientEventTest { - private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) { - ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java) - TestUtils.assertParcelingIsLossless(obj) - } - @Test fun testBuilderAndParcel() { val dhcpClientEvent = DhcpClientEvent.Builder() @@ -45,6 +38,6 @@ class DhcpClientEventTest { assertEquals(FAKE_MESSAGE, dhcpClientEvent.msg) assertEquals(Integer.MAX_VALUE, dhcpClientEvent.durationMs) - testParcel(dhcpClientEvent, 2) + assertParcelSane(dhcpClientEvent, 2) } } diff --git a/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt b/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt index e9d5e6db1c7e..236f72eafbdc 100644 --- a/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt +++ b/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt @@ -1,10 +1,10 @@ package android.net.metrics -import android.net.metrics.DhcpErrorEvent.errorCodeWithOption import android.net.metrics.DhcpErrorEvent.DHCP_INVALID_OPTION_LENGTH +import android.net.metrics.DhcpErrorEvent.errorCodeWithOption import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 -import com.android.internal.util.TestUtils.parcelingRoundTrip +import com.android.testutils.parcelingRoundTrip import java.lang.reflect.Modifier import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull @@ -62,4 +62,4 @@ class DhcpErrorEventTest { fun testToString_InvalidErrorCode() { assertNotNull(DhcpErrorEvent(TEST_ERROR_CODE).toString()) } -}
\ No newline at end of file +} diff --git a/tests/net/common/java/android/net/metrics/IpManagerEventTest.kt b/tests/net/common/java/android/net/metrics/IpManagerEventTest.kt index 5144ca56bf28..64be50837fc9 100644 --- a/tests/net/common/java/android/net/metrics/IpManagerEventTest.kt +++ b/tests/net/common/java/android/net/metrics/IpManagerEventTest.kt @@ -16,11 +16,9 @@ package android.net.metrics -import android.os.Parcelable import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 -import com.android.internal.util.ParcelableTestUtil -import com.android.internal.util.TestUtils +import com.android.testutils.assertParcelSane import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith @@ -28,11 +26,6 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) @SmallTest class IpManagerEventTest { - private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) { - ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java) - TestUtils.assertParcelingIsLossless(obj) - } - @Test fun testConstructorAndParcel() { (IpManagerEvent.PROVISIONING_OK..IpManagerEvent.ERROR_INTERFACE_NOT_FOUND).forEach { @@ -40,7 +33,7 @@ class IpManagerEventTest { assertEquals(it, ipManagerEvent.eventType) assertEquals(Long.MAX_VALUE, ipManagerEvent.durationMs) - testParcel(ipManagerEvent, 2) + assertParcelSane(ipManagerEvent, 2) } } } diff --git a/tests/net/common/java/android/net/metrics/IpReachabilityEventTest.kt b/tests/net/common/java/android/net/metrics/IpReachabilityEventTest.kt index d76ebf67ff1d..55b5e492dd47 100644 --- a/tests/net/common/java/android/net/metrics/IpReachabilityEventTest.kt +++ b/tests/net/common/java/android/net/metrics/IpReachabilityEventTest.kt @@ -16,11 +16,9 @@ package android.net.metrics -import android.os.Parcelable import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 -import com.android.internal.util.ParcelableTestUtil -import com.android.internal.util.TestUtils +import com.android.testutils.assertParcelSane import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith @@ -28,18 +26,13 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) @SmallTest class IpReachabilityEventTest { - private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) { - ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java) - TestUtils.assertParcelingIsLossless(obj) - } - @Test fun testConstructorAndParcel() { (IpReachabilityEvent.PROBE..IpReachabilityEvent.PROVISIONING_LOST_ORGANIC).forEach { val ipReachabilityEvent = IpReachabilityEvent(it) assertEquals(it, ipReachabilityEvent.eventType) - testParcel(ipReachabilityEvent, 1) + assertParcelSane(ipReachabilityEvent, 1) } } } diff --git a/tests/net/common/java/android/net/metrics/NetworkEventTest.kt b/tests/net/common/java/android/net/metrics/NetworkEventTest.kt index 8b52e81eea1e..41430b03a1eb 100644 --- a/tests/net/common/java/android/net/metrics/NetworkEventTest.kt +++ b/tests/net/common/java/android/net/metrics/NetworkEventTest.kt @@ -16,11 +16,9 @@ package android.net.metrics -import android.os.Parcelable import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 -import com.android.internal.util.ParcelableTestUtil -import com.android.internal.util.TestUtils +import com.android.testutils.assertParcelSane import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith @@ -28,11 +26,6 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) @SmallTest class NetworkEventTest { - private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) { - ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java) - TestUtils.assertParcelingIsLossless(obj) - } - @Test fun testConstructorAndParcel() { (NetworkEvent.NETWORK_CONNECTED..NetworkEvent.NETWORK_PARTIAL_CONNECTIVITY).forEach { @@ -44,7 +37,7 @@ class NetworkEventTest { assertEquals(it, networkEvent.eventType) assertEquals(Long.MAX_VALUE, networkEvent.durationMs) - testParcel(networkEvent, 2) + assertParcelSane(networkEvent, 2) } } } diff --git a/tests/net/common/java/android/net/metrics/RaEventTest.kt b/tests/net/common/java/android/net/metrics/RaEventTest.kt index f38d32844230..d9b720332fbe 100644 --- a/tests/net/common/java/android/net/metrics/RaEventTest.kt +++ b/tests/net/common/java/android/net/metrics/RaEventTest.kt @@ -16,11 +16,9 @@ package android.net.metrics -import android.os.Parcelable import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 -import com.android.internal.util.ParcelableTestUtil -import com.android.internal.util.TestUtils +import com.android.testutils.assertParcelSane import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith @@ -30,11 +28,6 @@ private const val NO_LIFETIME: Long = -1L @RunWith(AndroidJUnit4::class) @SmallTest class RaEventTest { - private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) { - ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java) - TestUtils.assertParcelingIsLossless(obj) - } - @Test fun testConstructorAndParcel() { var raEvent = RaEvent.Builder().build() @@ -74,6 +67,6 @@ class RaEventTest { assertEquals(5, raEvent.rdnssLifetime) assertEquals(6, raEvent.dnsslLifetime) - testParcel(raEvent, 6) + assertParcelSane(raEvent, 6) } } diff --git a/tests/net/common/java/android/net/metrics/ValidationProbeEventTest.kt b/tests/net/common/java/android/net/metrics/ValidationProbeEventTest.kt index c0cef8fe91fd..51c0d41bf4d5 100644 --- a/tests/net/common/java/android/net/metrics/ValidationProbeEventTest.kt +++ b/tests/net/common/java/android/net/metrics/ValidationProbeEventTest.kt @@ -16,11 +16,9 @@ package android.net.metrics -import android.os.Parcelable import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 -import com.android.internal.util.ParcelableTestUtil -import com.android.internal.util.TestUtils +import com.android.testutils.assertParcelSane import java.lang.reflect.Modifier import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue @@ -33,11 +31,6 @@ private const val REVALIDATION: Int = 2 shl 8 @RunWith(AndroidJUnit4::class) @SmallTest class ValidationProbeEventTest { - private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) { - ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java) - TestUtils.assertParcelingIsLossless(obj) - } - private infix fun Int.hasType(type: Int) = (type and this) == type @Test @@ -58,7 +51,7 @@ class ValidationProbeEventTest { assertTrue(validationProbeEvent.probeType hasType FIRST_VALIDATION) assertEquals(ValidationProbeEvent.DNS_SUCCESS, validationProbeEvent.returnCode) - testParcel(validationProbeEvent, 3) + assertParcelSane(validationProbeEvent, 3) } @Test diff --git a/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt b/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt new file mode 100644 index 000000000000..7b22e45db90a --- /dev/null +++ b/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.netstats + +import android.net.NetworkStats +import android.net.NetworkStats.DEFAULT_NETWORK_NO +import android.net.NetworkStats.DEFAULT_NETWORK_YES +import android.net.NetworkStats.Entry +import android.net.NetworkStats.IFACE_VT +import android.net.NetworkStats.METERED_NO +import android.net.NetworkStats.METERED_YES +import android.net.NetworkStats.ROAMING_NO +import android.net.NetworkStats.ROAMING_YES +import android.net.NetworkStats.SET_DEFAULT +import android.net.NetworkStats.SET_FOREGROUND +import android.net.NetworkStats.TAG_NONE +import android.os.Build +import androidx.test.filters.SmallTest +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.assertFieldCountEquals +import com.android.testutils.assertNetworkStatsEquals +import com.android.testutils.assertParcelingIsLossless +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import kotlin.test.assertEquals + +@RunWith(JUnit4::class) +@SmallTest +class NetworkStatsApiTest { + @Rule + @JvmField + val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.Q) + + private val testStatsEmpty = NetworkStats(0L, 0) + + // Note that these variables need to be initialized outside of constructor, initialize + // here with methods that don't exist in Q devices will result in crash. + + // stats1 and stats2 will have some entries with common keys, which are expected to + // be merged if performing add on these 2 stats. + private lateinit var testStats1: NetworkStats + private lateinit var testStats2: NetworkStats + + // This is a result of adding stats1 and stats2, while the merging of common key items is + // subject to test later, this should not be initialized with for a loop to add stats1 + // and stats2 above. + private lateinit var testStats3: NetworkStats + + companion object { + private const val TEST_IFACE = "test0" + private const val TEST_UID1 = 1001 + private const val TEST_UID2 = 1002 + } + + @Before + fun setUp() { + testStats1 = NetworkStats(0L, 0) + // Entries which only appear in set1. + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 37, 52, 1, 10, 4)) + // Entries which are common for set1 and set2. + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 17, 2, 11, 1, 0)) + .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 40, 1, 0, 0, 8)) + .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 1, 6, 2, 0)) + assertEquals(8, testStats1.size()) + + testStats2 = NetworkStats(0L, 0) + // Entries which are common for set1 and set2. + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 15, 2, 31, 1)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45)) + .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 11, 2, 3, 4, 7)) + .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4, 3, 2, 1, 0)) + // Entry which only appears in set2. + .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0)) + assertEquals(5, testStats2.size()) + + testStats3 = NetworkStats(0L, 9) + // Entries which are unique either in stats1 or stats2. + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2)) + .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0)) + // Entries which are common for stats1 and stats2 are being merged. + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 20, 17, 13, 32, 1)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 50, 113, 11, 11, 49)) + .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 51, 3, 3, 4, 15)) + .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 7, 4, 8, 3, 0)) + assertEquals(9, testStats3.size()) + } + + @Test + fun testAddEntry() { + val expectedEntriesInStats2 = arrayOf( + Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 15, 2, 31, 1), + Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45), + Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 11, 2, 3, 4, 7), + Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4, 3, 2, 1, 0), + Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0)) + + // While testStats* are already initialized with addEntry, verify content added + // matches expectation. + for (i in expectedEntriesInStats2.indices) { + val entry = testStats2.getValues(i, null) + assertEquals(expectedEntriesInStats2[i], entry) + } + + // Verify entry updated with addEntry. + val stats = testStats2.addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 12, -5, 7, 0, 9)) + assertEquals(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 16, -2, 9, 1, 9), + stats.getValues(3, null)) + } + + @Test + fun testAdd() { + var stats = NetworkStats(0L, 0) + assertNetworkStatsEquals(testStatsEmpty, stats) + stats = stats.add(testStats2) + assertNetworkStatsEquals(testStats2, stats) + stats = stats.add(testStats1) + // EMPTY + STATS2 + STATS1 = STATS3 + assertNetworkStatsEquals(testStats3, stats) + } + + @Test + fun testParcelUnparcel() { + assertParcelingIsLossless(testStatsEmpty) + assertParcelingIsLossless(testStats1) + assertParcelingIsLossless(testStats2) + assertFieldCountEquals(15, NetworkStats::class.java) + } + + @Test + fun testDescribeContents() { + assertEquals(0, testStatsEmpty.describeContents()) + assertEquals(0, testStats1.describeContents()) + assertEquals(0, testStats2.describeContents()) + assertEquals(0, testStats3.describeContents()) + } + + @Test + fun testSubtract() { + // STATS3 - STATS2 = STATS1 + assertNetworkStatsEquals(testStats1, testStats3.subtract(testStats2)) + // STATS3 - STATS1 = STATS2 + assertNetworkStatsEquals(testStats2, testStats3.subtract(testStats1)) + } + + @Test + fun testMethodsDontModifyReceiver() { + listOf(testStatsEmpty, testStats1, testStats2, testStats3).forEach { + val origStats = it.clone() + it.addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45)) + it.add(testStats3) + it.subtract(testStats1) + assertNetworkStatsEquals(origStats, it) + } + } +}
\ No newline at end of file diff --git a/tests/net/common/java/android/net/util/SocketUtilsTest.kt b/tests/net/common/java/android/net/util/SocketUtilsTest.kt index 9c7cfb0c716e..aaf97f36889b 100644 --- a/tests/net/common/java/android/net/util/SocketUtilsTest.kt +++ b/tests/net/common/java/android/net/util/SocketUtilsTest.kt @@ -14,8 +14,9 @@ * limitations under the License. */ -package android.net.util; +package android.net.util +import android.os.Build import android.system.NetlinkSocketAddress import android.system.Os import android.system.OsConstants.AF_INET @@ -26,18 +27,26 @@ import android.system.OsConstants.SOCK_DGRAM import android.system.PacketSocketAddress import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Assert.fail +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith private const val TEST_INDEX = 123 private const val TEST_PORT = 555 +private const val FF_BYTE = 0xff.toByte() + @RunWith(AndroidJUnit4::class) @SmallTest class SocketUtilsTest { + @Rule @JvmField + val ignoreRule = DevSdkIgnoreRule() + @Test fun testMakeNetlinkSocketAddress() { val nlAddress = SocketUtils.makeNetlinkSocketAddress(TEST_PORT, RTMGRP_NEIGH) @@ -50,16 +59,21 @@ class SocketUtilsTest { } @Test - fun testMakePacketSocketAddress() { + fun testMakePacketSocketAddress_Q() { val pkAddress = SocketUtils.makePacketSocketAddress(ETH_P_ALL, TEST_INDEX) assertTrue("Not PacketSocketAddress object", pkAddress is PacketSocketAddress) - val ff = 0xff.toByte() - val pkAddress2 = SocketUtils.makePacketSocketAddress(TEST_INDEX, - byteArrayOf(ff, ff, ff, ff, ff, ff)) + val pkAddress2 = SocketUtils.makePacketSocketAddress(TEST_INDEX, ByteArray(6) { FF_BYTE }) assertTrue("Not PacketSocketAddress object", pkAddress2 is PacketSocketAddress) } + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testMakePacketSocketAddress() { + val pkAddress = SocketUtils.makePacketSocketAddress( + ETH_P_ALL, TEST_INDEX, ByteArray(6) { FF_BYTE }) + assertTrue("Not PacketSocketAddress object", pkAddress is PacketSocketAddress) + } + @Test fun testCloseSocket() { // Expect no exception happening with null object. diff --git a/tests/net/util/Android.bp b/tests/net/deflake/Android.bp index d8c502d46871..b1b017131c64 100644 --- a/tests/net/util/Android.bp +++ b/tests/net/deflake/Android.bp @@ -14,17 +14,17 @@ // limitations under the License. // -// Common utilities for network tests. -java_library { - name: "frameworks-net-testutils", - srcs: ["java/**/*.java"], - // test_current to be also appropriate for CTS tests - sdk_version: "test_current", - static_libs: [ - "androidx.annotation_annotation", +java_test_host { + name: "FrameworksNetDeflakeTest", + srcs: ["src/**/*.kt"], + libs: [ "junit", + "tradefed", ], - libs: [ - "android.test.base.stubs", + static_libs: [ + "kotlin-test", + "net-host-tests-utils", ], -}
\ No newline at end of file + data: [":FrameworksNetTests"], + test_suites: ["device-tests"], +} diff --git a/tests/net/deflake/src/com/android/server/net/FrameworksNetDeflakeTest.kt b/tests/net/deflake/src/com/android/server/net/FrameworksNetDeflakeTest.kt new file mode 100644 index 000000000000..62855255fec2 --- /dev/null +++ b/tests/net/deflake/src/com/android/server/net/FrameworksNetDeflakeTest.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.net + +import com.android.testutils.host.DeflakeHostTestBase +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner +import org.junit.runner.RunWith + +@RunWith(DeviceJUnit4ClassRunner::class) +class FrameworksNetDeflakeTest: DeflakeHostTestBase() { + override val runCount = 20 + override val testApkFilename = "FrameworksNetTests.apk" + override val testClasses = listOf("com.android.server.ConnectivityServiceTest") +}
\ No newline at end of file diff --git a/tests/net/integration/Android.bp b/tests/net/integration/Android.bp new file mode 100644 index 000000000000..874bd4b97df1 --- /dev/null +++ b/tests/net/integration/Android.bp @@ -0,0 +1,65 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +android_test { + name: "FrameworksNetIntegrationTests", + platform_apis: true, + certificate: "platform", + srcs: [ + "src/**/*.kt", + "src/**/*.aidl", + ], + libs: [ + "android.test.mock", + ], + static_libs: [ + "TestNetworkStackLib", + "androidx.test.ext.junit", + "frameworks-net-integration-testutils", + "kotlin-reflect", + "mockito-target-extended-minus-junit4", + "net-tests-utils", + "services.core", + "services.net", + "testables", + ], + test_suites: ["device-tests"], + use_embedded_native_libs: true, + jni_libs: [ + // For mockito extended + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + // android_library does not include JNI libs: include NetworkStack dependencies here + "libnativehelper_compat_libc++", + "libnetworkstackutilsjni", + ], +} + +// Utilities for testing framework code both in integration and unit tests. +java_library { + name: "frameworks-net-integration-testutils", + srcs: ["util/**/*.java", "util/**/*.kt"], + static_libs: [ + "androidx.annotation_annotation", + "androidx.test.rules", + "junit", + "net-tests-utils", + ], + libs: [ + "services.core", + "services.net", + ], +} diff --git a/tests/net/integration/AndroidManifest.xml b/tests/net/integration/AndroidManifest.xml new file mode 100644 index 000000000000..09c0e4826075 --- /dev/null +++ b/tests/net/integration/AndroidManifest.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.net.integrationtests"> + + <!-- For ConnectivityService registerReceiverAsUser (receiving broadcasts) --> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> + <!-- PermissionMonitor sets network permissions for each user --> + <uses-permission android:name="android.permission.MANAGE_USERS" /> + <!-- ConnectivityService sends notifications to BatteryStats --> + <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" /> + <!-- Reading network status --> + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.NETWORK_FACTORY" /> + <uses-permission android:name="android.permission.NETWORK_STACK" /> + <uses-permission android:name="android.permission.OBSERVE_NETWORK_POLICY" /> + <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" /> + <!-- Reading DeviceConfig flags --> + <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> + <application android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + + <!-- This manifest is merged with the base manifest of the real NetworkStack app. + Remove the NetworkStackService from the base (real) manifest, and replace with a test + service that responds to the same intent --> + <service android:name=".TestNetworkStackService" + android:process="com.android.server.net.integrationtests.testnetworkstack"> + <intent-filter> + <action android:name="android.net.INetworkStackConnector.Test"/> + </intent-filter> + </service> + <service android:name=".NetworkStackInstrumentationService" + android:process="com.android.server.net.integrationtests.testnetworkstack"> + <intent-filter> + <action android:name=".INetworkStackInstrumentation"/> + </intent-filter> + </service> + <service android:name="com.android.server.connectivity.ipmemorystore.RegularMaintenanceJobService" + android:process="com.android.server.net.integrationtests.testnetworkstack" + android:permission="android.permission.BIND_JOB_SERVICE"/> + + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.net.integrationtests" + android:label="Frameworks Net Integration Tests" /> + +</manifest> diff --git a/tests/net/integration/res/values/config.xml b/tests/net/integration/res/values/config.xml new file mode 100644 index 000000000000..2c8046ffd781 --- /dev/null +++ b/tests/net/integration/res/values/config.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- + Override configuration for testing. The below settings use the config_ variants, which are + normally used by RROs to override the setting with highest priority. --> + <integer name="config_captive_portal_dns_probe_timeout">12500</integer> + <string name="config_captive_portal_http_url" translatable="false">http://test.android.com</string> + <string name="config_captive_portal_https_url" translatable="false">https://secure.test.android.com</string> + <string-array name="config_captive_portal_fallback_urls" translatable="false"> + <item>http://fallback1.android.com</item> + <item>http://fallback2.android.com</item> + </string-array> + <string-array name="config_captive_portal_fallback_probe_specs" translatable="false"> + </string-array> +</resources> diff --git a/tests/net/integration/src/android/net/TestNetworkStackClient.kt b/tests/net/integration/src/android/net/TestNetworkStackClient.kt new file mode 100644 index 000000000000..01eb514a1c81 --- /dev/null +++ b/tests/net/integration/src/android/net/TestNetworkStackClient.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.net + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.os.IBinder +import com.android.server.net.integrationtests.TestNetworkStackService +import org.mockito.Mockito.any +import org.mockito.Mockito.spy +import org.mockito.Mockito.timeout +import org.mockito.Mockito.verify +import kotlin.test.fail + +const val TEST_ACTION_SUFFIX = ".Test" + +class TestNetworkStackClient(context: Context) : NetworkStackClient(TestDependencies(context)) { + // TODO: consider switching to TrackRecord for more expressive checks + private val lastCallbacks = HashMap<Network, INetworkMonitorCallbacks>() + + private class TestDependencies(private val context: Context) : Dependencies { + override fun addToServiceManager(service: IBinder) = Unit + override fun checkCallerUid() = Unit + + override fun getConnectivityModuleConnector(): ConnectivityModuleConnector { + return ConnectivityModuleConnector { _, _, _, inSystemProcess -> + getNetworkStackIntent(inSystemProcess) + }.also { it.init(context) } + } + + private fun getNetworkStackIntent(inSystemProcess: Boolean): Intent? { + // Simulate out-of-system-process config: in-process service not found (null intent) + if (inSystemProcess) return null + val intent = Intent(INetworkStackConnector::class.qualifiedName + TEST_ACTION_SUFFIX) + val serviceName = TestNetworkStackService::class.qualifiedName + ?: fail("TestNetworkStackService name not found") + intent.component = ComponentName(context.packageName, serviceName) + return intent + } + } + + // base may be an instance of an inaccessible subclass, so non-spyable. + // Use a known open class that delegates to the original instance for all methods except + // asBinder. asBinder needs to use its own non-delegated implementation as otherwise it would + // return a binder token to a class that is not spied on. + open class NetworkMonitorCallbacksWrapper(private val base: INetworkMonitorCallbacks) : + INetworkMonitorCallbacks.Stub(), INetworkMonitorCallbacks by base { + // asBinder is implemented by both base class and delegate: specify explicitly + override fun asBinder(): IBinder { + return super.asBinder() + } + } + + override fun makeNetworkMonitor(network: Network, name: String?, cb: INetworkMonitorCallbacks) { + val cbSpy = spy(NetworkMonitorCallbacksWrapper(cb)) + lastCallbacks[network] = cbSpy + super.makeNetworkMonitor(network, name, cbSpy) + } + + fun verifyNetworkMonitorCreated(network: Network, timeoutMs: Long) { + val cb = lastCallbacks[network] + ?: fail("NetworkMonitor for network $network not requested") + verify(cb, timeout(timeoutMs)).onNetworkMonitorCreated(any()) + } +}
\ No newline at end of file diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt new file mode 100644 index 000000000000..344d0c3260c9 --- /dev/null +++ b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.net.integrationtests + +import android.content.ComponentName +import android.content.Context +import android.content.Context.BIND_AUTO_CREATE +import android.content.Context.BIND_IMPORTANT +import android.content.Intent +import android.content.ServiceConnection +import android.net.ConnectivityManager +import android.net.IDnsResolver +import android.net.INetd +import android.net.INetworkPolicyManager +import android.net.INetworkStatsService +import android.net.LinkProperties +import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET +import android.net.NetworkCapabilities.TRANSPORT_CELLULAR +import android.net.NetworkRequest +import android.net.TestNetworkStackClient +import android.net.metrics.IpConnectivityLog +import android.os.ConditionVariable +import android.os.IBinder +import android.os.INetworkManagementService +import android.testing.TestableContext +import android.util.Log +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.ConnectivityService +import com.android.server.LocalServices +import com.android.server.NetworkAgentWrapper +import com.android.server.TestNetIdManager +import com.android.server.connectivity.DefaultNetworkMetrics +import com.android.server.connectivity.IpConnectivityMetrics +import com.android.server.connectivity.MockableSystemProperties +import com.android.server.connectivity.ProxyTracker +import com.android.server.net.NetworkPolicyManagerInternal +import com.android.testutils.TestableNetworkCallback +import org.junit.After +import org.junit.Before +import org.junit.BeforeClass +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.doNothing +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock +import org.mockito.Mockito.spy +import org.mockito.MockitoAnnotations +import org.mockito.Spy +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import kotlin.test.fail + +const val SERVICE_BIND_TIMEOUT_MS = 5_000L +const val TEST_TIMEOUT_MS = 1_000L + +/** + * Test that exercises an instrumented version of ConnectivityService against an instrumented + * NetworkStack in a different test process. + */ +@RunWith(AndroidJUnit4::class) +class ConnectivityServiceIntegrationTest { + // lateinit used here for mocks as they need to be reinitialized between each test and the test + // should crash if they are used before being initialized. + @Mock + private lateinit var netManager: INetworkManagementService + @Mock + private lateinit var statsService: INetworkStatsService + @Mock + private lateinit var policyManager: INetworkPolicyManager + @Mock + private lateinit var log: IpConnectivityLog + @Mock + private lateinit var netd: INetd + @Mock + private lateinit var dnsResolver: IDnsResolver + @Mock + private lateinit var metricsLogger: IpConnectivityMetrics.Logger + @Mock + private lateinit var defaultMetrics: DefaultNetworkMetrics + @Spy + private var context = TestableContext(realContext) + + // lateinit for these three classes under test, as they should be reset to a different instance + // for every test but should always be initialized before use (or the test should crash). + private lateinit var networkStackClient: TestNetworkStackClient + private lateinit var service: ConnectivityService + private lateinit var cm: ConnectivityManager + + companion object { + // lateinit for this binder token, as it must be initialized before any test code is run + // and use of it before init should crash the test. + private lateinit var nsInstrumentation: INetworkStackInstrumentation + private val bindingCondition = ConditionVariable(false) + + private val realContext get() = InstrumentationRegistry.getInstrumentation().context + + private class InstrumentationServiceConnection : ServiceConnection { + override fun onServiceConnected(name: ComponentName?, service: IBinder?) { + Log.i("TestNetworkStack", "Service connected") + try { + if (service == null) fail("Error binding to NetworkStack instrumentation") + if (::nsInstrumentation.isInitialized) fail("Service already connected") + nsInstrumentation = INetworkStackInstrumentation.Stub.asInterface(service) + } finally { + bindingCondition.open() + } + } + + override fun onServiceDisconnected(name: ComponentName?) = Unit + } + + @BeforeClass + @JvmStatic + fun setUpClass() { + val intent = Intent(realContext, NetworkStackInstrumentationService::class.java) + intent.action = INetworkStackInstrumentation::class.qualifiedName + assertTrue(realContext.bindService(intent, InstrumentationServiceConnection(), + BIND_AUTO_CREATE or BIND_IMPORTANT), + "Error binding to instrumentation service") + assertTrue(bindingCondition.block(SERVICE_BIND_TIMEOUT_MS), + "Timed out binding to instrumentation service " + + "after $SERVICE_BIND_TIMEOUT_MS ms") + } + } + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + doReturn(defaultMetrics).`when`(metricsLogger).defaultNetworkMetrics() + doNothing().`when`(context).sendStickyBroadcastAsUser(any(), any(), any()) + + networkStackClient = TestNetworkStackClient(realContext) + networkStackClient.init() + networkStackClient.start() + + LocalServices.removeServiceForTest(NetworkPolicyManagerInternal::class.java) + LocalServices.addService(NetworkPolicyManagerInternal::class.java, + mock(NetworkPolicyManagerInternal::class.java)) + + service = TestConnectivityService(makeDependencies()) + cm = ConnectivityManager(context, service) + context.addMockSystemService(Context.CONNECTIVITY_SERVICE, cm) + + service.systemReady() + } + + private inner class TestConnectivityService(deps: Dependencies) : ConnectivityService( + context, netManager, statsService, policyManager, dnsResolver, log, netd, deps) + + private fun makeDependencies(): ConnectivityService.Dependencies { + val deps = spy(ConnectivityService.Dependencies()) + doReturn(networkStackClient).`when`(deps).networkStack + doReturn(metricsLogger).`when`(deps).metricsLogger + doReturn(mock(ProxyTracker::class.java)).`when`(deps).makeProxyTracker(any(), any()) + doReturn(mock(MockableSystemProperties::class.java)).`when`(deps).systemProperties + doReturn(TestNetIdManager()).`when`(deps).makeNetIdManager() + return deps + } + + @After + fun tearDown() { + nsInstrumentation.clearAllState() + } + + @Test + fun testValidation() { + val request = NetworkRequest.Builder() + .clearCapabilities() + .addCapability(NET_CAPABILITY_INTERNET) + .build() + val testCallback = TestableNetworkCallback() + + cm.registerNetworkCallback(request, testCallback) + nsInstrumentation.addHttpResponse(HttpResponse( + "http://test.android.com", + responseCode = 204, contentLength = 42, redirectUrl = null)) + nsInstrumentation.addHttpResponse(HttpResponse( + "https://secure.test.android.com", + responseCode = 204, contentLength = 42, redirectUrl = null)) + + val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, LinkProperties(), null /* ncTemplate */, + context) + networkStackClient.verifyNetworkMonitorCreated(na.network, TEST_TIMEOUT_MS) + + na.addCapability(NET_CAPABILITY_INTERNET) + na.connect() + + testCallback.expectAvailableThenValidatedCallbacks(na.network, TEST_TIMEOUT_MS) + assertEquals(2, nsInstrumentation.getRequestUrls().size) + } +} diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.aidl b/tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.aidl new file mode 100644 index 000000000000..9a2bcfea7641 --- /dev/null +++ b/tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.net.integrationtests; + +parcelable HttpResponse;
\ No newline at end of file diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.kt b/tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.kt new file mode 100644 index 000000000000..45073d8df3f7 --- /dev/null +++ b/tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.net.integrationtests + +import android.os.Parcel +import android.os.Parcelable + +data class HttpResponse( + val requestUrl: String, + val responseCode: Int, + val contentLength: Long, + val redirectUrl: String? +) : Parcelable { + constructor(p: Parcel): this(p.readString(), p.readInt(), p.readLong(), p.readString()) + + override fun writeToParcel(dest: Parcel, flags: Int) { + with(dest) { + writeString(requestUrl) + writeInt(responseCode) + writeLong(contentLength) + writeString(redirectUrl) + } + } + + override fun describeContents() = 0 + companion object CREATOR : Parcelable.Creator<HttpResponse> { + override fun createFromParcel(source: Parcel) = HttpResponse(source) + override fun newArray(size: Int) = arrayOfNulls<HttpResponse?>(size) + } +}
\ No newline at end of file diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/INetworkStackInstrumentation.aidl b/tests/net/integration/src/com/android/server/net/integrationtests/INetworkStackInstrumentation.aidl new file mode 100644 index 000000000000..efc58add9cf5 --- /dev/null +++ b/tests/net/integration/src/com/android/server/net/integrationtests/INetworkStackInstrumentation.aidl @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.net.integrationtests; + +import com.android.server.net.integrationtests.HttpResponse; + +interface INetworkStackInstrumentation { + void clearAllState(); + void addHttpResponse(in HttpResponse response); + List<String> getRequestUrls(); +}
\ No newline at end of file diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt b/tests/net/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt new file mode 100644 index 000000000000..4827d2997d93 --- /dev/null +++ b/tests/net/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.net.integrationtests + +import android.app.Service +import android.content.Intent +import java.net.URL +import java.util.Collections +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentLinkedQueue +import kotlin.collections.ArrayList +import kotlin.test.fail + +/** + * An instrumentation interface for the NetworkStack that allows controlling behavior to + * facilitate integration tests. + */ +class NetworkStackInstrumentationService : Service() { + override fun onBind(intent: Intent) = InstrumentationConnector.asBinder() + + object InstrumentationConnector : INetworkStackInstrumentation.Stub() { + private val httpResponses = ConcurrentHashMap<String, ConcurrentLinkedQueue<HttpResponse>>() + .run { + withDefault { key -> getOrPut(key) { ConcurrentLinkedQueue() } } + } + private val httpRequestUrls = Collections.synchronizedList(ArrayList<String>()) + + /** + * Called when an HTTP request is being processed by NetworkMonitor. Returns the response + * that should be simulated. + */ + fun processRequest(url: URL): HttpResponse { + val strUrl = url.toString() + httpRequestUrls.add(strUrl) + return httpResponses[strUrl]?.poll() + ?: fail("No mocked response for request: $strUrl. " + + "Mocked URL keys are: ${httpResponses.keys}") + } + + /** + * Clear all state of this connector. This is intended for use between two tests, so all + * state should be reset as if the connector was just created. + */ + override fun clearAllState() { + httpResponses.clear() + httpRequestUrls.clear() + } + + /** + * Add a response to a future HTTP request. + * + * <p>For any subsequent HTTP/HTTPS query, the first response with a matching URL will be + * used to mock the query response. + */ + override fun addHttpResponse(response: HttpResponse) { + httpResponses.getValue(response.requestUrl).add(response) + } + + /** + * Get the ordered list of request URLs that have been sent by NetworkMonitor, and were + * answered based on mock responses. + */ + override fun getRequestUrls(): List<String> { + return ArrayList(httpRequestUrls) + } + } +}
\ No newline at end of file diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt b/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt new file mode 100644 index 000000000000..649f71d4293f --- /dev/null +++ b/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.net.integrationtests + +import android.app.Service +import android.content.Context +import android.content.Intent +import android.net.INetworkMonitorCallbacks +import android.net.Network +import android.net.metrics.IpConnectivityLog +import android.net.util.SharedLog +import android.os.IBinder +import com.android.networkstack.netlink.TcpSocketTracker +import com.android.server.NetworkStackService +import com.android.server.NetworkStackService.NetworkMonitorConnector +import com.android.server.NetworkStackService.NetworkStackConnector +import com.android.server.connectivity.NetworkMonitor +import com.android.server.net.integrationtests.NetworkStackInstrumentationService.InstrumentationConnector +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock +import org.mockito.Mockito.spy +import java.net.HttpURLConnection +import java.net.URL +import java.net.URLConnection + +private const val TEST_NETID = 42 + +/** + * Android service that can return an [android.net.INetworkStackConnector] which can be instrumented + * through [NetworkStackInstrumentationService]. + * Useful in tests to create test instrumented NetworkStack components that can receive + * instrumentation commands through [NetworkStackInstrumentationService]. + */ +class TestNetworkStackService : Service() { + override fun onBind(intent: Intent): IBinder = TestNetworkStackConnector(makeTestContext()) + + private fun makeTestContext() = spy(applicationContext).also { + doReturn(mock(IBinder::class.java)).`when`(it).getSystemService(Context.NETD_SERVICE) + } + + private class TestPermissionChecker : NetworkStackService.PermissionChecker() { + override fun enforceNetworkStackCallingPermission() = Unit + } + + private class NetworkMonitorDeps(private val privateDnsBypassNetwork: Network) : + NetworkMonitor.Dependencies() { + override fun getPrivateDnsBypassNetwork(network: Network?) = privateDnsBypassNetwork + } + + private inner class TestNetworkStackConnector(context: Context) : NetworkStackConnector( + context, TestPermissionChecker(), NetworkStackService.Dependencies()) { + + private val network = Network(TEST_NETID) + private val privateDnsBypassNetwork = TestNetwork(TEST_NETID) + + private inner class TestNetwork(netId: Int) : Network(netId) { + override fun openConnection(url: URL): URLConnection { + val response = InstrumentationConnector.processRequest(url) + + val connection = mock(HttpURLConnection::class.java) + doReturn(response.responseCode).`when`(connection).responseCode + doReturn(response.contentLength).`when`(connection).contentLengthLong + doReturn(response.redirectUrl).`when`(connection).getHeaderField("location") + return connection + } + } + + override fun makeNetworkMonitor( + network: Network, + name: String?, + cb: INetworkMonitorCallbacks + ) { + val nm = NetworkMonitor(this@TestNetworkStackService, cb, + this.network, + mock(IpConnectivityLog::class.java), mock(SharedLog::class.java), + mock(NetworkStackService.NetworkStackServiceManager::class.java), + NetworkMonitorDeps(privateDnsBypassNetwork), + mock(TcpSocketTracker::class.java)) + cb.onNetworkMonitorCreated(NetworkMonitorConnector(nm, TestPermissionChecker())) + } + } +} diff --git a/tests/net/integration/util/com/android/server/ConnectivityServiceTestUtils.kt b/tests/net/integration/util/com/android/server/ConnectivityServiceTestUtils.kt new file mode 100644 index 000000000000..fa2b99ce5cc6 --- /dev/null +++ b/tests/net/integration/util/com/android/server/ConnectivityServiceTestUtils.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server + +import android.net.ConnectivityManager.TYPE_BLUETOOTH +import android.net.ConnectivityManager.TYPE_ETHERNET +import android.net.ConnectivityManager.TYPE_MOBILE +import android.net.ConnectivityManager.TYPE_NONE +import android.net.ConnectivityManager.TYPE_TEST +import android.net.ConnectivityManager.TYPE_VPN +import android.net.ConnectivityManager.TYPE_WIFI +import android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH +import android.net.NetworkCapabilities.TRANSPORT_CELLULAR +import android.net.NetworkCapabilities.TRANSPORT_ETHERNET +import android.net.NetworkCapabilities.TRANSPORT_TEST +import android.net.NetworkCapabilities.TRANSPORT_VPN +import android.net.NetworkCapabilities.TRANSPORT_WIFI + +fun transportToLegacyType(transport: Int) = when (transport) { + TRANSPORT_BLUETOOTH -> TYPE_BLUETOOTH + TRANSPORT_CELLULAR -> TYPE_MOBILE + TRANSPORT_ETHERNET -> TYPE_ETHERNET + TRANSPORT_TEST -> TYPE_TEST + TRANSPORT_VPN -> TYPE_VPN + TRANSPORT_WIFI -> TYPE_WIFI + else -> TYPE_NONE +}
\ No newline at end of file diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java new file mode 100644 index 000000000000..03954de98438 --- /dev/null +++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; +import static android.net.NetworkCapabilities.TRANSPORT_VPN; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; + +import static com.android.server.ConnectivityServiceTestUtilsKt.transportToLegacyType; + +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkAgent; +import android.net.NetworkAgentConfig; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.net.NetworkProvider; +import android.net.NetworkSpecifier; +import android.net.SocketKeepalive; +import android.net.UidRange; +import android.os.ConditionVariable; +import android.os.HandlerThread; +import android.os.Message; +import android.util.Log; + +import com.android.server.connectivity.ConnectivityConstants; +import com.android.testutils.HandlerUtilsKt; +import com.android.testutils.TestableNetworkCallback; + +import java.util.Set; + +public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { + private final NetworkInfo mNetworkInfo; + private final NetworkCapabilities mNetworkCapabilities; + private final HandlerThread mHandlerThread; + private final Context mContext; + private final String mLogTag; + + private final ConditionVariable mDisconnected = new ConditionVariable(); + private final ConditionVariable mPreventReconnectReceived = new ConditionVariable(); + private int mScore; + private NetworkAgent mNetworkAgent; + private int mStartKeepaliveError = SocketKeepalive.ERROR_UNSUPPORTED; + private int mStopKeepaliveError = SocketKeepalive.NO_KEEPALIVE; + private Integer mExpectedKeepaliveSlot = null; + + public NetworkAgentWrapper(int transport, LinkProperties linkProperties, + NetworkCapabilities ncTemplate, Context context) throws Exception { + final int type = transportToLegacyType(transport); + final String typeName = ConnectivityManager.getNetworkTypeName(type); + mNetworkInfo = new NetworkInfo(type, 0, typeName, "Mock"); + mNetworkCapabilities = (ncTemplate != null) ? ncTemplate : new NetworkCapabilities(); + mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_SUSPENDED); + mNetworkCapabilities.addTransportType(transport); + switch (transport) { + case TRANSPORT_ETHERNET: + mScore = 70; + break; + case TRANSPORT_WIFI: + mScore = 60; + break; + case TRANSPORT_CELLULAR: + mScore = 50; + break; + case TRANSPORT_WIFI_AWARE: + mScore = 20; + break; + case TRANSPORT_VPN: + mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_VPN); + // VPNs deduce the SUSPENDED capability from their underlying networks and there + // is no public API to let VPN services set it. + mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_SUSPENDED); + mScore = ConnectivityConstants.VPN_DEFAULT_SCORE; + break; + default: + throw new UnsupportedOperationException("unimplemented network type"); + } + mContext = context; + mLogTag = "Mock-" + typeName; + mHandlerThread = new HandlerThread(mLogTag); + mHandlerThread.start(); + + mNetworkAgent = makeNetworkAgent(linkProperties); + } + + protected InstrumentedNetworkAgent makeNetworkAgent(LinkProperties linkProperties) + throws Exception { + return new InstrumentedNetworkAgent(this, linkProperties); + } + + public static class InstrumentedNetworkAgent extends NetworkAgent { + private final NetworkAgentWrapper mWrapper; + + public InstrumentedNetworkAgent(NetworkAgentWrapper wrapper, LinkProperties lp) { + super(wrapper.mHandlerThread.getLooper(), wrapper.mContext, wrapper.mLogTag, + wrapper.mNetworkInfo, wrapper.mNetworkCapabilities, lp, wrapper.mScore, + new NetworkAgentConfig(), NetworkProvider.ID_NONE); + mWrapper = wrapper; + } + + @Override + public void unwanted() { + mWrapper.mDisconnected.open(); + } + + @Override + public void startSocketKeepalive(Message msg) { + int slot = msg.arg1; + if (mWrapper.mExpectedKeepaliveSlot != null) { + assertEquals((int) mWrapper.mExpectedKeepaliveSlot, slot); + } + onSocketKeepaliveEvent(slot, mWrapper.mStartKeepaliveError); + } + + @Override + public void stopSocketKeepalive(Message msg) { + onSocketKeepaliveEvent(msg.arg1, mWrapper.mStopKeepaliveError); + } + + @Override + protected void preventAutomaticReconnect() { + mWrapper.mPreventReconnectReceived.open(); + } + + @Override + protected void addKeepalivePacketFilter(Message msg) { + Log.i(mWrapper.mLogTag, "Add keepalive packet filter."); + } + + @Override + protected void removeKeepalivePacketFilter(Message msg) { + Log.i(mWrapper.mLogTag, "Remove keepalive packet filter."); + } + } + + public void adjustScore(int change) { + mScore += change; + mNetworkAgent.sendNetworkScore(mScore); + } + + public int getScore() { + return mScore; + } + + public void explicitlySelected(boolean explicitlySelected, boolean acceptUnvalidated) { + mNetworkAgent.explicitlySelected(explicitlySelected, acceptUnvalidated); + } + + public void addCapability(int capability) { + mNetworkCapabilities.addCapability(capability); + mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); + } + + public void removeCapability(int capability) { + mNetworkCapabilities.removeCapability(capability); + mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); + } + + public void setUids(Set<UidRange> uids) { + mNetworkCapabilities.setUids(uids); + mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); + } + + public void setSignalStrength(int signalStrength) { + mNetworkCapabilities.setSignalStrength(signalStrength); + mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); + } + + public void setNetworkSpecifier(NetworkSpecifier networkSpecifier) { + mNetworkCapabilities.setNetworkSpecifier(networkSpecifier); + mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); + } + + public void setNetworkCapabilities(NetworkCapabilities nc, boolean sendToConnectivityService) { + mNetworkCapabilities.set(nc); + if (sendToConnectivityService) { + mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); + } + } + + public void connect() { + assertNotEquals("MockNetworkAgents can only be connected once", + getNetworkInfo().getDetailedState(), NetworkInfo.DetailedState.CONNECTED); + mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null); + mNetworkAgent.sendNetworkInfo(mNetworkInfo); + } + + public void suspend() { + removeCapability(NET_CAPABILITY_NOT_SUSPENDED); + } + + public void resume() { + addCapability(NET_CAPABILITY_NOT_SUSPENDED); + } + + public void disconnect() { + mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null); + mNetworkAgent.sendNetworkInfo(mNetworkInfo); + } + + @Override + public Network getNetwork() { + return mNetworkAgent.getNetwork(); + } + + public void expectPreventReconnectReceived(long timeoutMs) { + assertTrue(mPreventReconnectReceived.block(timeoutMs)); + } + + public void expectDisconnected(long timeoutMs) { + assertTrue(mDisconnected.block(timeoutMs)); + } + + public void sendLinkProperties(LinkProperties lp) { + mNetworkAgent.sendLinkProperties(lp); + } + + public void setStartKeepaliveEvent(int reason) { + mStartKeepaliveError = reason; + } + + public void setStopKeepaliveEvent(int reason) { + mStopKeepaliveError = reason; + } + + public void setExpectedKeepaliveSlot(Integer slot) { + mExpectedKeepaliveSlot = slot; + } + + public NetworkAgent getNetworkAgent() { + return mNetworkAgent; + } + + public NetworkInfo getNetworkInfo() { + return mNetworkInfo; + } + + public NetworkCapabilities getNetworkCapabilities() { + return mNetworkCapabilities; + } + + public void waitForIdle(long timeoutMs) { + HandlerUtilsKt.waitForIdle(mHandlerThread, timeoutMs); + } +} diff --git a/tests/net/integration/util/com/android/server/TestNetIdManager.kt b/tests/net/integration/util/com/android/server/TestNetIdManager.kt new file mode 100644 index 000000000000..938a694e8ba9 --- /dev/null +++ b/tests/net/integration/util/com/android/server/TestNetIdManager.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server + +import java.util.concurrent.atomic.AtomicInteger + +/** + * A [NetIdManager] that generates ID starting from [NetIdManager.MAX_NET_ID] and decreasing, rather + * than starting from [NetIdManager.MIN_NET_ID] and increasing. + * + * Useful for testing ConnectivityService, to minimize the risk of test ConnectivityService netIDs + * overlapping with netIDs used by the real ConnectivityService on the device. + * + * IDs may still overlap if many networks have been used on the device (so the "real" netIDs + * are close to MAX_NET_ID), but this is typically not the case when running unit tests. Also, there + * is no way to fully solve the overlap issue without altering ID allocation in non-test code, as + * the real ConnectivityService could start using netIds that have been used by the test in the + * past. + */ +class TestNetIdManager : NetIdManager() { + private val nextId = AtomicInteger(MAX_NET_ID) + override fun reserveNetId() = nextId.decrementAndGet() + override fun releaseNetId(id: Int) = Unit + fun peekNextNetId() = nextId.get() - 1 +} diff --git a/tests/net/java/android/app/usage/NetworkStatsManagerTest.java b/tests/net/java/android/app/usage/NetworkStatsManagerTest.java index fd555c1e9115..899295a019d2 100644 --- a/tests/net/java/android/app/usage/NetworkStatsManagerTest.java +++ b/tests/net/java/android/app/usage/NetworkStatsManagerTest.java @@ -202,8 +202,7 @@ public class NetworkStatsManagerTest { assertFalse(stats.hasNextBucket()); } - private void assertBucketMatches(Entry expected, - NetworkStats.Bucket actual) { + private void assertBucketMatches(Entry expected, NetworkStats.Bucket actual) { assertEquals(expected.uid, actual.getUid()); assertEquals(expected.rxBytes, actual.getRxBytes()); assertEquals(expected.rxPackets, actual.getRxPackets()); diff --git a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java new file mode 100644 index 000000000000..1d6c10766792 --- /dev/null +++ b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsBinder; +import static android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback; +import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport; +import static android.net.ConnectivityDiagnosticsManager.DataStallReport; + +import static com.android.testutils.ParcelUtilsKt.assertParcelSane; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import android.content.Context; +import android.os.PersistableBundle; + +import androidx.test.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; + +import java.util.concurrent.Executor; + +@RunWith(JUnit4.class) +public class ConnectivityDiagnosticsManagerTest { + private static final int NET_ID = 1; + private static final int DETECTION_METHOD = 2; + private static final long TIMESTAMP = 10L; + private static final String INTERFACE_NAME = "interface"; + private static final String BUNDLE_KEY = "key"; + private static final String BUNDLE_VALUE = "value"; + + private static final Executor INLINE_EXECUTOR = x -> x.run(); + + @Mock private IConnectivityManager mService; + @Mock private ConnectivityDiagnosticsCallback mCb; + + private Context mContext; + private ConnectivityDiagnosticsBinder mBinder; + private ConnectivityDiagnosticsManager mManager; + + private String mPackageName; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getContext(); + + mService = mock(IConnectivityManager.class); + mCb = mock(ConnectivityDiagnosticsCallback.class); + + mBinder = new ConnectivityDiagnosticsBinder(mCb, INLINE_EXECUTOR); + mManager = new ConnectivityDiagnosticsManager(mContext, mService); + + mPackageName = mContext.getOpPackageName(); + } + + @After + public void tearDown() { + // clear ConnectivityDiagnosticsManager callbacks map + ConnectivityDiagnosticsManager.sCallbacks.clear(); + } + + private ConnectivityReport createSampleConnectivityReport() { + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setInterfaceName(INTERFACE_NAME); + + final NetworkCapabilities networkCapabilities = new NetworkCapabilities(); + networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS); + + final PersistableBundle bundle = new PersistableBundle(); + bundle.putString(BUNDLE_KEY, BUNDLE_VALUE); + + return new ConnectivityReport( + new Network(NET_ID), TIMESTAMP, linkProperties, networkCapabilities, bundle); + } + + private ConnectivityReport createDefaultConnectivityReport() { + return new ConnectivityReport( + new Network(0), + 0L, + new LinkProperties(), + new NetworkCapabilities(), + PersistableBundle.EMPTY); + } + + @Test + public void testPersistableBundleEquals() { + assertFalse( + ConnectivityDiagnosticsManager.persistableBundleEquals( + null, PersistableBundle.EMPTY)); + assertFalse( + ConnectivityDiagnosticsManager.persistableBundleEquals( + PersistableBundle.EMPTY, null)); + assertTrue( + ConnectivityDiagnosticsManager.persistableBundleEquals( + PersistableBundle.EMPTY, PersistableBundle.EMPTY)); + + final PersistableBundle a = new PersistableBundle(); + a.putString(BUNDLE_KEY, BUNDLE_VALUE); + + final PersistableBundle b = new PersistableBundle(); + b.putString(BUNDLE_KEY, BUNDLE_VALUE); + + final PersistableBundle c = new PersistableBundle(); + c.putString(BUNDLE_KEY, null); + + assertFalse( + ConnectivityDiagnosticsManager.persistableBundleEquals(PersistableBundle.EMPTY, a)); + assertFalse( + ConnectivityDiagnosticsManager.persistableBundleEquals(a, PersistableBundle.EMPTY)); + + assertTrue(ConnectivityDiagnosticsManager.persistableBundleEquals(a, b)); + assertTrue(ConnectivityDiagnosticsManager.persistableBundleEquals(b, a)); + + assertFalse(ConnectivityDiagnosticsManager.persistableBundleEquals(a, c)); + assertFalse(ConnectivityDiagnosticsManager.persistableBundleEquals(c, a)); + } + + @Test + public void testConnectivityReportEquals() { + final ConnectivityReport defaultReport = createDefaultConnectivityReport(); + final ConnectivityReport sampleReport = createSampleConnectivityReport(); + assertEquals(sampleReport, createSampleConnectivityReport()); + assertEquals(defaultReport, createDefaultConnectivityReport()); + + final LinkProperties linkProperties = sampleReport.getLinkProperties(); + final NetworkCapabilities networkCapabilities = sampleReport.getNetworkCapabilities(); + final PersistableBundle bundle = sampleReport.getAdditionalInfo(); + + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(NET_ID), + 0L, + new LinkProperties(), + new NetworkCapabilities(), + PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(0), + TIMESTAMP, + new LinkProperties(), + new NetworkCapabilities(), + PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(0), + 0L, + linkProperties, + new NetworkCapabilities(), + PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(0), + TIMESTAMP, + new LinkProperties(), + networkCapabilities, + PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(0), + TIMESTAMP, + new LinkProperties(), + new NetworkCapabilities(), + bundle)); + } + + @Test + public void testConnectivityReportParcelUnparcel() { + assertParcelSane(createSampleConnectivityReport(), 5); + } + + private DataStallReport createSampleDataStallReport() { + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setInterfaceName(INTERFACE_NAME); + + final PersistableBundle bundle = new PersistableBundle(); + bundle.putString(BUNDLE_KEY, BUNDLE_VALUE); + + final NetworkCapabilities networkCapabilities = new NetworkCapabilities(); + networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS); + + return new DataStallReport( + new Network(NET_ID), + TIMESTAMP, + DETECTION_METHOD, + linkProperties, + networkCapabilities, + bundle); + } + + private DataStallReport createDefaultDataStallReport() { + return new DataStallReport( + new Network(0), + 0L, + 0, + new LinkProperties(), + new NetworkCapabilities(), + PersistableBundle.EMPTY); + } + + @Test + public void testDataStallReportEquals() { + final DataStallReport defaultReport = createDefaultDataStallReport(); + final DataStallReport sampleReport = createSampleDataStallReport(); + assertEquals(sampleReport, createSampleDataStallReport()); + assertEquals(defaultReport, createDefaultDataStallReport()); + + final LinkProperties linkProperties = sampleReport.getLinkProperties(); + final NetworkCapabilities networkCapabilities = sampleReport.getNetworkCapabilities(); + final PersistableBundle bundle = sampleReport.getStallDetails(); + + assertNotEquals( + defaultReport, + new DataStallReport( + new Network(NET_ID), + 0L, + 0, + new LinkProperties(), + new NetworkCapabilities(), + PersistableBundle.EMPTY)); + assertNotEquals( + defaultReport, + new DataStallReport( + new Network(0), + TIMESTAMP, + 0, + new LinkProperties(), + new NetworkCapabilities(), + PersistableBundle.EMPTY)); + assertNotEquals( + defaultReport, + new DataStallReport( + new Network(0), + 0L, + DETECTION_METHOD, + new LinkProperties(), + new NetworkCapabilities(), + PersistableBundle.EMPTY)); + assertNotEquals( + defaultReport, + new DataStallReport( + new Network(0), + 0L, + 0, + linkProperties, + new NetworkCapabilities(), + PersistableBundle.EMPTY)); + assertNotEquals( + defaultReport, + new DataStallReport( + new Network(0), + 0L, + 0, + new LinkProperties(), + networkCapabilities, + PersistableBundle.EMPTY)); + assertNotEquals( + defaultReport, + new DataStallReport( + new Network(0), + 0L, + 0, + new LinkProperties(), + new NetworkCapabilities(), + bundle)); + } + + @Test + public void testDataStallReportParcelUnparcel() { + assertParcelSane(createSampleDataStallReport(), 6); + } + + @Test + public void testConnectivityDiagnosticsCallbackOnConnectivityReportAvailable() { + mBinder.onConnectivityReportAvailable(createSampleConnectivityReport()); + + // The callback will be invoked synchronously by inline executor. Immediately check the + // latch without waiting. + verify(mCb).onConnectivityReportAvailable(eq(createSampleConnectivityReport())); + } + + @Test + public void testConnectivityDiagnosticsCallbackOnDataStallSuspected() { + mBinder.onDataStallSuspected(createSampleDataStallReport()); + + // The callback will be invoked synchronously by inline executor. Immediately check the + // latch without waiting. + verify(mCb).onDataStallSuspected(eq(createSampleDataStallReport())); + } + + @Test + public void testConnectivityDiagnosticsCallbackOnNetworkConnectivityReported() { + final Network n = new Network(NET_ID); + final boolean connectivity = true; + + mBinder.onNetworkConnectivityReported(n, connectivity); + + // The callback will be invoked synchronously by inline executor. Immediately check the + // latch without waiting. + verify(mCb).onNetworkConnectivityReported(eq(n), eq(connectivity)); + } + + @Test + public void testRegisterConnectivityDiagnosticsCallback() throws Exception { + final NetworkRequest request = new NetworkRequest.Builder().build(); + + mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb); + + verify(mService).registerConnectivityDiagnosticsCallback( + any(ConnectivityDiagnosticsBinder.class), eq(request), eq(mPackageName)); + assertTrue(ConnectivityDiagnosticsManager.sCallbacks.containsKey(mCb)); + } + + @Test + public void testRegisterDuplicateConnectivityDiagnosticsCallback() throws Exception { + final NetworkRequest request = new NetworkRequest.Builder().build(); + + mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb); + + try { + mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb); + fail("Duplicate callback registration should fail"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testUnregisterConnectivityDiagnosticsCallback() throws Exception { + final NetworkRequest request = new NetworkRequest.Builder().build(); + mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb); + + mManager.unregisterConnectivityDiagnosticsCallback(mCb); + + verify(mService).unregisterConnectivityDiagnosticsCallback( + any(ConnectivityDiagnosticsBinder.class)); + assertFalse(ConnectivityDiagnosticsManager.sCallbacks.containsKey(mCb)); + + // verify that re-registering is successful + mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb); + verify(mService, times(2)).registerConnectivityDiagnosticsCallback( + any(ConnectivityDiagnosticsBinder.class), eq(request), eq(mPackageName)); + assertTrue(ConnectivityDiagnosticsManager.sCallbacks.containsKey(mCb)); + } + + @Test + public void testUnregisterUnknownConnectivityDiagnosticsCallback() throws Exception { + mManager.unregisterConnectivityDiagnosticsCallback(mCb); + + verifyNoMoreInteractions(mService); + } +} diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java index 7ede14428a4f..d6bf334ee56a 100644 --- a/tests/net/java/android/net/ConnectivityManagerTest.java +++ b/tests/net/java/android/net/ConnectivityManagerTest.java @@ -212,7 +212,8 @@ public class ConnectivityManagerTest { ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class); // register callback - when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt())) + when(mService.requestNetwork( + any(), captor.capture(), anyInt(), any(), anyInt(), any())) .thenReturn(request); manager.requestNetwork(request, callback, handler); @@ -240,7 +241,8 @@ public class ConnectivityManagerTest { ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class); // register callback - when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt())) + when(mService.requestNetwork( + any(), captor.capture(), anyInt(), any(), anyInt(), any())) .thenReturn(req1); manager.requestNetwork(req1, callback, handler); @@ -258,7 +260,8 @@ public class ConnectivityManagerTest { verify(callback, timeout(100).times(0)).onLosing(any(), anyInt()); // callback can be registered again - when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt())) + when(mService.requestNetwork( + any(), captor.capture(), anyInt(), any(), anyInt(), any())) .thenReturn(req2); manager.requestNetwork(req2, callback, handler); @@ -282,7 +285,8 @@ public class ConnectivityManagerTest { info.targetSdkVersion = VERSION_CODES.N_MR1 + 1; when(mCtx.getApplicationInfo()).thenReturn(info); - when(mService.requestNetwork(any(), any(), anyInt(), any(), anyInt())).thenReturn(request); + when(mService.requestNetwork(any(), any(), anyInt(), any(), anyInt(), any())) + .thenReturn(request); Handler handler = new Handler(Looper.getMainLooper()); manager.requestNetwork(request, callback, handler); diff --git a/tests/net/java/android/net/DnsPacketTest.java b/tests/net/java/android/net/DnsPacketTest.java deleted file mode 100644 index 975abf416944..000000000000 --- a/tests/net/java/android/net/DnsPacketTest.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.Arrays; -import java.util.List; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class DnsPacketTest { - private void assertHeaderParses(DnsPacket.DnsHeader header, int id, int flag, - int qCount, int aCount, int nsCount, int arCount) { - assertEquals(header.id, id); - assertEquals(header.flags, flag); - assertEquals(header.getRecordCount(DnsPacket.QDSECTION), qCount); - assertEquals(header.getRecordCount(DnsPacket.ANSECTION), aCount); - assertEquals(header.getRecordCount(DnsPacket.NSSECTION), nsCount); - assertEquals(header.getRecordCount(DnsPacket.ARSECTION), arCount); - } - - private void assertRecordParses(DnsPacket.DnsRecord record, String dname, - int dtype, int dclass, int ttl, byte[] rr) { - assertEquals(record.dName, dname); - assertEquals(record.nsType, dtype); - assertEquals(record.nsClass, dclass); - assertEquals(record.ttl, ttl); - assertTrue(Arrays.equals(record.getRR(), rr)); - } - - class TestDnsPacket extends DnsPacket { - TestDnsPacket(byte[] data) throws ParseException { - super(data); - } - - public DnsHeader getHeader() { - return mHeader; - } - public List<DnsRecord> getRecordList(int secType) { - return mRecords[secType]; - } - } - - @Test - public void testNullDisallowed() { - try { - new TestDnsPacket(null); - fail("Exception not thrown for null byte array"); - } catch (ParseException e) { - } - } - - @Test - public void testV4Answer() throws Exception { - final byte[] v4blob = new byte[] { - /* Header */ - 0x55, 0x66, /* Transaction ID */ - (byte) 0x81, (byte) 0x80, /* Flags */ - 0x00, 0x01, /* Questions */ - 0x00, 0x01, /* Answer RRs */ - 0x00, 0x00, /* Authority RRs */ - 0x00, 0x00, /* Additional RRs */ - /* Queries */ - 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65, - 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */ - 0x00, 0x01, /* Type */ - 0x00, 0x01, /* Class */ - /* Answers */ - (byte) 0xc0, 0x0c, /* Name */ - 0x00, 0x01, /* Type */ - 0x00, 0x01, /* Class */ - 0x00, 0x00, 0x01, 0x2b, /* TTL */ - 0x00, 0x04, /* Data length */ - (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */ - }; - TestDnsPacket packet = new TestDnsPacket(v4blob); - - // Header part - assertHeaderParses(packet.getHeader(), 0x5566, 0x8180, 1, 1, 0, 0); - - // Record part - List<DnsPacket.DnsRecord> qdRecordList = - packet.getRecordList(DnsPacket.QDSECTION); - assertEquals(qdRecordList.size(), 1); - assertRecordParses(qdRecordList.get(0), "www.google.com", 1, 1, 0, null); - - List<DnsPacket.DnsRecord> anRecordList = - packet.getRecordList(DnsPacket.ANSECTION); - assertEquals(anRecordList.size(), 1); - assertRecordParses(anRecordList.get(0), "www.google.com", 1, 1, 0x12b, - new byte[]{ (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 }); - } - - @Test - public void testV6Answer() throws Exception { - final byte[] v6blob = new byte[] { - /* Header */ - 0x77, 0x22, /* Transaction ID */ - (byte) 0x81, (byte) 0x80, /* Flags */ - 0x00, 0x01, /* Questions */ - 0x00, 0x01, /* Answer RRs */ - 0x00, 0x00, /* Authority RRs */ - 0x00, 0x00, /* Additional RRs */ - /* Queries */ - 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65, - 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */ - 0x00, 0x1c, /* Type */ - 0x00, 0x01, /* Class */ - /* Answers */ - (byte) 0xc0, 0x0c, /* Name */ - 0x00, 0x1c, /* Type */ - 0x00, 0x01, /* Class */ - 0x00, 0x00, 0x00, 0x37, /* TTL */ - 0x00, 0x10, /* Data length */ - 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 /* Address */ - }; - TestDnsPacket packet = new TestDnsPacket(v6blob); - - // Header part - assertHeaderParses(packet.getHeader(), 0x7722, 0x8180, 1, 1, 0, 0); - - // Record part - List<DnsPacket.DnsRecord> qdRecordList = - packet.getRecordList(DnsPacket.QDSECTION); - assertEquals(qdRecordList.size(), 1); - assertRecordParses(qdRecordList.get(0), "www.google.com", 28, 1, 0, null); - - List<DnsPacket.DnsRecord> anRecordList = - packet.getRecordList(DnsPacket.ANSECTION); - assertEquals(anRecordList.size(), 1); - assertRecordParses(anRecordList.get(0), "www.google.com", 28, 1, 0x37, - new byte[]{ 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 }); - } -} diff --git a/tests/net/java/android/net/Ikev2VpnProfileTest.java b/tests/net/java/android/net/Ikev2VpnProfileTest.java new file mode 100644 index 000000000000..ada5494efd60 --- /dev/null +++ b/tests/net/java/android/net/Ikev2VpnProfileTest.java @@ -0,0 +1,437 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.test.mock.MockContext; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.net.VpnProfile; +import com.android.org.bouncycastle.x509.X509V1CertificateGenerator; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import javax.security.auth.x500.X500Principal; + +/** Unit tests for {@link Ikev2VpnProfile.Builder}. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class Ikev2VpnProfileTest { + private static final String SERVER_ADDR_STRING = "1.2.3.4"; + private static final String IDENTITY_STRING = "Identity"; + private static final String USERNAME_STRING = "username"; + private static final String PASSWORD_STRING = "pa55w0rd"; + private static final String EXCL_LIST = "exclList"; + private static final byte[] PSK_BYTES = "preSharedKey".getBytes(); + private static final int TEST_MTU = 1300; + + private final MockContext mMockContext = + new MockContext() { + @Override + public String getOpPackageName() { + return "fooPackage"; + } + }; + private final ProxyInfo mProxy = new ProxyInfo(SERVER_ADDR_STRING, -1, EXCL_LIST); + + private X509Certificate mUserCert; + private X509Certificate mServerRootCa; + private PrivateKey mPrivateKey; + + @Before + public void setUp() throws Exception { + mServerRootCa = generateRandomCertAndKeyPair().cert; + + final CertificateAndKey userCertKey = generateRandomCertAndKeyPair(); + mUserCert = userCertKey.cert; + mPrivateKey = userCertKey.key; + } + + private Ikev2VpnProfile.Builder getBuilderWithDefaultOptions() { + final Ikev2VpnProfile.Builder builder = + new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING); + + builder.setBypassable(true); + builder.setProxy(mProxy); + builder.setMaxMtu(TEST_MTU); + builder.setMetered(true); + + return builder; + } + + @Test + public void testBuildValidProfileWithOptions() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final Ikev2VpnProfile profile = builder.build(); + assertNotNull(profile); + + // Check non-auth parameters correctly stored + assertEquals(SERVER_ADDR_STRING, profile.getServerAddr()); + assertEquals(IDENTITY_STRING, profile.getUserIdentity()); + assertEquals(mProxy, profile.getProxyInfo()); + assertTrue(profile.isBypassable()); + assertTrue(profile.isMetered()); + assertEquals(TEST_MTU, profile.getMaxMtu()); + assertEquals(Ikev2VpnProfile.DEFAULT_ALGORITHMS, profile.getAllowedAlgorithms()); + } + + @Test + public void testBuildUsernamePasswordProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final Ikev2VpnProfile profile = builder.build(); + assertNotNull(profile); + + assertEquals(USERNAME_STRING, profile.getUsername()); + assertEquals(PASSWORD_STRING, profile.getPassword()); + assertEquals(mServerRootCa, profile.getServerRootCaCert()); + + assertNull(profile.getPresharedKey()); + assertNull(profile.getRsaPrivateKey()); + assertNull(profile.getUserCert()); + } + + @Test + public void testBuildDigitalSignatureProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); + final Ikev2VpnProfile profile = builder.build(); + assertNotNull(profile); + + assertEquals(profile.getUserCert(), mUserCert); + assertEquals(mPrivateKey, profile.getRsaPrivateKey()); + assertEquals(profile.getServerRootCaCert(), mServerRootCa); + + assertNull(profile.getPresharedKey()); + assertNull(profile.getUsername()); + assertNull(profile.getPassword()); + } + + @Test + public void testBuildPresharedKeyProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthPsk(PSK_BYTES); + final Ikev2VpnProfile profile = builder.build(); + assertNotNull(profile); + + assertArrayEquals(PSK_BYTES, profile.getPresharedKey()); + + assertNull(profile.getServerRootCaCert()); + assertNull(profile.getUsername()); + assertNull(profile.getPassword()); + assertNull(profile.getRsaPrivateKey()); + assertNull(profile.getUserCert()); + } + + @Test + public void testBuildWithAllowedAlgorithmsAead() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + builder.setAuthPsk(PSK_BYTES); + + List<String> allowedAlgorithms = Arrays.asList(IpSecAlgorithm.AUTH_CRYPT_AES_GCM); + builder.setAllowedAlgorithms(allowedAlgorithms); + + final Ikev2VpnProfile profile = builder.build(); + assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms()); + } + + @Test + public void testBuildWithAllowedAlgorithmsNormal() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + builder.setAuthPsk(PSK_BYTES); + + List<String> allowedAlgorithms = + Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA512, IpSecAlgorithm.CRYPT_AES_CBC); + builder.setAllowedAlgorithms(allowedAlgorithms); + + final Ikev2VpnProfile profile = builder.build(); + assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms()); + } + + @Test + public void testSetAllowedAlgorithmsEmptyList() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + try { + builder.setAllowedAlgorithms(new ArrayList<>()); + fail("Expected exception due to no valid algorithm set"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testSetAllowedAlgorithmsInvalidList() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + List<String> allowedAlgorithms = new ArrayList<>(); + + try { + builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA256)); + fail("Expected exception due to missing encryption"); + } catch (IllegalArgumentException expected) { + } + + try { + builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.CRYPT_AES_CBC)); + fail("Expected exception due to missing authentication"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testSetAllowedAlgorithmsInsecureAlgorithm() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + List<String> allowedAlgorithms = new ArrayList<>(); + + try { + builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_MD5)); + fail("Expected exception due to insecure algorithm"); + } catch (IllegalArgumentException expected) { + } + + try { + builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA1)); + fail("Expected exception due to insecure algorithm"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testBuildNoAuthMethodSet() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + try { + builder.build(); + fail("Expected exception due to lack of auth method"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testBuildInvalidMtu() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + try { + builder.setMaxMtu(500); + fail("Expected exception due to too-small MTU"); + } catch (IllegalArgumentException expected) { + } + } + + private void verifyVpnProfileCommon(VpnProfile profile) { + assertEquals(SERVER_ADDR_STRING, profile.server); + assertEquals(IDENTITY_STRING, profile.ipsecIdentifier); + assertEquals(mProxy, profile.proxy); + assertTrue(profile.isBypassable); + assertTrue(profile.isMetered); + assertEquals(TEST_MTU, profile.maxMtu); + } + + @Test + public void testPskConvertToVpnProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthPsk(PSK_BYTES); + final VpnProfile profile = builder.build().toVpnProfile(); + + verifyVpnProfileCommon(profile); + assertEquals(Ikev2VpnProfile.encodeForIpsecSecret(PSK_BYTES), profile.ipsecSecret); + + // Check nothing else is set + assertEquals("", profile.username); + assertEquals("", profile.password); + assertEquals("", profile.ipsecUserCert); + assertEquals("", profile.ipsecCaCert); + } + + @Test + public void testUsernamePasswordConvertToVpnProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final VpnProfile profile = builder.build().toVpnProfile(); + + verifyVpnProfileCommon(profile); + assertEquals(USERNAME_STRING, profile.username); + assertEquals(PASSWORD_STRING, profile.password); + assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert); + + // Check nothing else is set + assertEquals("", profile.ipsecUserCert); + assertEquals("", profile.ipsecSecret); + } + + @Test + public void testRsaConvertToVpnProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); + final VpnProfile profile = builder.build().toVpnProfile(); + + final String expectedSecret = Ikev2VpnProfile.PREFIX_INLINE + + Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded()); + verifyVpnProfileCommon(profile); + assertEquals(Ikev2VpnProfile.certificateToPemString(mUserCert), profile.ipsecUserCert); + assertEquals( + expectedSecret, + profile.ipsecSecret); + assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert); + + // Check nothing else is set + assertEquals("", profile.username); + assertEquals("", profile.password); + } + + @Test + public void testPskFromVpnProfileDiscardsIrrelevantValues() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthPsk(PSK_BYTES); + final VpnProfile profile = builder.build().toVpnProfile(); + profile.username = USERNAME_STRING; + profile.password = PASSWORD_STRING; + profile.ipsecCaCert = Ikev2VpnProfile.certificateToPemString(mServerRootCa); + profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert); + + final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile); + assertNull(result.getUsername()); + assertNull(result.getPassword()); + assertNull(result.getUserCert()); + assertNull(result.getRsaPrivateKey()); + assertNull(result.getServerRootCaCert()); + } + + @Test + public void testUsernamePasswordFromVpnProfileDiscardsIrrelevantValues() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final VpnProfile profile = builder.build().toVpnProfile(); + profile.ipsecSecret = new String(PSK_BYTES); + profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert); + + final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile); + assertNull(result.getPresharedKey()); + assertNull(result.getUserCert()); + assertNull(result.getRsaPrivateKey()); + } + + @Test + public void testRsaFromVpnProfileDiscardsIrrelevantValues() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); + final VpnProfile profile = builder.build().toVpnProfile(); + profile.username = USERNAME_STRING; + profile.password = PASSWORD_STRING; + + final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile); + assertNull(result.getUsername()); + assertNull(result.getPassword()); + assertNull(result.getPresharedKey()); + } + + @Test + public void testPskConversionIsLossless() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthPsk(PSK_BYTES); + final Ikev2VpnProfile ikeProfile = builder.build(); + + assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile())); + } + + @Test + public void testUsernamePasswordConversionIsLossless() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final Ikev2VpnProfile ikeProfile = builder.build(); + + assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile())); + } + + @Test + public void testRsaConversionIsLossless() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); + final Ikev2VpnProfile ikeProfile = builder.build(); + + assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile())); + } + + private static class CertificateAndKey { + public final X509Certificate cert; + public final PrivateKey key; + + CertificateAndKey(X509Certificate cert, PrivateKey key) { + this.cert = cert; + this.key = key; + } + } + + private static CertificateAndKey generateRandomCertAndKeyPair() throws Exception { + final Date validityBeginDate = + new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1L)); + final Date validityEndDate = + new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1L)); + + // Generate a keypair + final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(512); + final KeyPair keyPair = keyPairGenerator.generateKeyPair(); + + final X500Principal dnName = new X500Principal("CN=test.android.com"); + final X509V1CertificateGenerator certGen = new X509V1CertificateGenerator(); + certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis())); + certGen.setSubjectDN(dnName); + certGen.setIssuerDN(dnName); + certGen.setNotBefore(validityBeginDate); + certGen.setNotAfter(validityEndDate); + certGen.setPublicKey(keyPair.getPublic()); + certGen.setSignatureAlgorithm("SHA256WithRSAEncryption"); + + final X509Certificate cert = certGen.generate(keyPair.getPrivate(), "AndroidOpenSSL"); + return new CertificateAndKey(cert, keyPair.getPrivate()); + } +} diff --git a/tests/net/java/android/net/IpMemoryStoreTest.java b/tests/net/java/android/net/IpMemoryStoreTest.java index b81ca36429ff..0b13800bc5c9 100644 --- a/tests/net/java/android/net/IpMemoryStoreTest.java +++ b/tests/net/java/android/net/IpMemoryStoreTest.java @@ -35,6 +35,7 @@ import android.net.ipmemorystore.IOnStatusListener; import android.net.ipmemorystore.NetworkAttributes; import android.net.ipmemorystore.NetworkAttributesParcelable; import android.net.ipmemorystore.Status; +import android.net.networkstack.ModuleNetworkStackClient; import android.os.RemoteException; import androidx.test.filters.SmallTest; @@ -67,7 +68,7 @@ public class IpMemoryStoreTest { @Mock Context mMockContext; @Mock - NetworkStackClient mNetworkStackClient; + ModuleNetworkStackClient mModuleNetworkStackClient; @Mock IIpMemoryStore mMockService; @Mock @@ -90,21 +91,21 @@ public class IpMemoryStoreTest { ((IIpMemoryStoreCallbacks) invocation.getArgument(0)) .onIpMemoryStoreFetched(mMockService); return null; - }).when(mNetworkStackClient).fetchIpMemoryStore(any()); + }).when(mModuleNetworkStackClient).fetchIpMemoryStore(any()); } else { - doNothing().when(mNetworkStackClient).fetchIpMemoryStore(mCbCaptor.capture()); + doNothing().when(mModuleNetworkStackClient).fetchIpMemoryStore(mCbCaptor.capture()); } mStore = new IpMemoryStore(mMockContext) { @Override - protected NetworkStackClient getNetworkStackClient() { - return mNetworkStackClient; + protected ModuleNetworkStackClient getModuleNetworkStackClient(Context ctx) { + return mModuleNetworkStackClient; } }; } private static NetworkAttributes buildTestNetworkAttributes(String hint, int mtu) { return new NetworkAttributes.Builder() - .setGroupHint(hint) + .setCluster(hint) .setMtu(mtu) .build(); } diff --git a/tests/net/java/android/net/IpSecConfigTest.java b/tests/net/java/android/net/IpSecConfigTest.java index 215506c05c88..c9888b24b6da 100644 --- a/tests/net/java/android/net/IpSecConfigTest.java +++ b/tests/net/java/android/net/IpSecConfigTest.java @@ -16,12 +16,12 @@ package android.net; +import static com.android.testutils.ParcelUtilsKt.assertParcelSane; +import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless; + import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import android.os.Parcel; import androidx.test.filters.SmallTest; @@ -89,23 +89,15 @@ public class IpSecConfigTest { IpSecConfig original = getSampleConfig(); IpSecConfig copy = new IpSecConfig(original); - assertTrue(IpSecConfig.equals(original, copy)); - assertFalse(original == copy); + assertEquals(original, copy); + assertNotSame(original, copy); } @Test - public void testParcelUnparcel() throws Exception { + public void testParcelUnparcel() { assertParcelingIsLossless(new IpSecConfig()); IpSecConfig c = getSampleConfig(); - assertParcelingIsLossless(c); - } - - private void assertParcelingIsLossless(IpSecConfig ci) throws Exception { - Parcel p = Parcel.obtain(); - ci.writeToParcel(p, 0); - p.setDataPosition(0); - IpSecConfig co = IpSecConfig.CREATOR.createFromParcel(p); - assertTrue(IpSecConfig.equals(co, ci)); + assertParcelSane(c, 15); } } diff --git a/tests/net/java/android/net/IpSecTransformTest.java b/tests/net/java/android/net/IpSecTransformTest.java index 2308a3c9b477..424f23dbbaf6 100644 --- a/tests/net/java/android/net/IpSecTransformTest.java +++ b/tests/net/java/android/net/IpSecTransformTest.java @@ -16,8 +16,8 @@ package android.net; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import androidx.test.filters.SmallTest; @@ -43,7 +43,7 @@ public class IpSecTransformTest { config.setSpiResourceId(1985); IpSecTransform postModification = new IpSecTransform(null, config); - assertFalse(IpSecTransform.equals(preModification, postModification)); + assertNotEquals(preModification, postModification); } @Test @@ -57,6 +57,6 @@ public class IpSecTransformTest { IpSecTransform config1 = new IpSecTransform(null, config); IpSecTransform config2 = new IpSecTransform(null, config); - assertTrue(IpSecTransform.equals(config1, config2)); + assertEquals(config1, config2); } } diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java index daf187d01533..91c9a2a38036 100644 --- a/tests/net/java/android/net/MacAddressTest.java +++ b/tests/net/java/android/net/MacAddressTest.java @@ -22,6 +22,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import android.net.util.MacAddressUtils; + import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -122,11 +124,11 @@ public class MacAddressTest { for (MacAddress mac : multicastAddresses) { String msg = mac.toString() + " expected to be a multicast address"; - assertTrue(msg, mac.isMulticastAddress()); + assertTrue(msg, MacAddressUtils.isMulticastAddress(mac)); } for (MacAddress mac : unicastAddresses) { String msg = mac.toString() + " expected not to be a multicast address"; - assertFalse(msg, mac.isMulticastAddress()); + assertFalse(msg, MacAddressUtils.isMulticastAddress(mac)); } } @@ -156,7 +158,7 @@ public class MacAddressTest { public void testMacAddressConversions() { final int iterations = 10000; for (int i = 0; i < iterations; i++) { - MacAddress mac = MacAddress.createRandomUnicastAddress(); + MacAddress mac = MacAddressUtils.createRandomUnicastAddress(); String stringRepr = mac.toString(); byte[] bytesRepr = mac.toByteArray(); @@ -188,7 +190,7 @@ public class MacAddressTest { final String expectedLocalOui = "26:5f:78"; final MacAddress base = MacAddress.fromString(anotherOui + ":0:0:0"); for (int i = 0; i < iterations; i++) { - MacAddress mac = MacAddress.createRandomUnicastAddress(base, r); + MacAddress mac = MacAddressUtils.createRandomUnicastAddress(base, r); String stringRepr = mac.toString(); assertTrue(stringRepr + " expected to be a locally assigned address", @@ -199,7 +201,7 @@ public class MacAddressTest { } for (int i = 0; i < iterations; i++) { - MacAddress mac = MacAddress.createRandomUnicastAddress(); + MacAddress mac = MacAddressUtils.createRandomUnicastAddress(); String stringRepr = mac.toString(); assertTrue(stringRepr + " expected to be a locally assigned address", diff --git a/tests/net/java/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java index c16a0f446651..735fa7cf3751 100644 --- a/tests/net/java/android/net/NetworkStatsTest.java +++ b/tests/net/java/android/net/NetworkStatsTest.java @@ -64,15 +64,15 @@ public class NetworkStatsTest { @Test public void testFindIndex() throws Exception { final NetworkStats stats = new NetworkStats(TEST_START, 5) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 1024L, 8L, 0L, 0L, 10) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 0L, 0L, 1024L, 8L, 11) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, 0L, 0L, 1024L, 8L, 11) - .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1024L, 8L, 1024L, 8L, 12) - .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, + .insertEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, DEFAULT_NETWORK_YES, 1024L, 8L, 1024L, 8L, 12); assertEquals(4, stats.findIndex(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_YES, @@ -94,21 +94,21 @@ public class NetworkStatsTest { @Test public void testFindIndexHinted() { final NetworkStats stats = new NetworkStats(TEST_START, 3) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 1024L, 8L, 0L, 0L, 10) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 0L, 0L, 1024L, 8L, 11) - .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 1024L, 8L, 1024L, 8L, 12) - .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1024L, 8L, 0L, 0L, 10) - .addValues(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 0L, 0L, 1024L, 8L, 11) - .addValues(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO, + .insertEntry(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 0L, 0L, 1024L, 8L, 11) - .addValues(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 1024L, 8L, 1024L, 8L, 12) - .addValues(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, + .insertEntry(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, DEFAULT_NETWORK_NO, 1024L, 8L, 1024L, 8L, 12); // verify that we correctly find across regardless of hinting @@ -143,27 +143,27 @@ public class NetworkStatsTest { assertEquals(0, stats.size()); assertEquals(4, stats.internalSize()); - stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 1L, 1L, 2L, 2L, 3); - stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2L, 2L, 2L, 2L, 4); - stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, + stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, DEFAULT_NETWORK_YES, 3L, 3L, 2L, 2L, 5); - stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, + stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, DEFAULT_NETWORK_NO, 3L, 3L, 2L, 2L, 5); assertEquals(4, stats.size()); assertEquals(4, stats.internalSize()); - stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4L, 40L, 4L, 40L, 7); - stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 5L, 50L, 4L, 40L, 8); - stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 6L, 60L, 5L, 50L, 10); - stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, + stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, DEFAULT_NETWORK_YES, 7L, 70L, 5L, 50L, 11); - stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, + stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, DEFAULT_NETWORK_NO, 7L, 70L, 5L, 50L, 11); assertEquals(9, stats.size()); @@ -193,8 +193,8 @@ public class NetworkStatsTest { public void testCombineExisting() throws Exception { final NetworkStats stats = new NetworkStats(TEST_START, 10); - stats.addValues(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 10); - stats.addValues(TEST_IFACE, 1001, SET_DEFAULT, 0xff, 128L, 1L, 128L, 1L, 2); + stats.insertEntry(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 10); + stats.insertEntry(TEST_IFACE, 1001, SET_DEFAULT, 0xff, 128L, 1L, 128L, 1L, 2); stats.combineValues(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, -128L, -1L, -128L, -1L, -1); @@ -215,12 +215,12 @@ public class NetworkStatsTest { @Test public void testSubtractIdenticalData() throws Exception { final NetworkStats before = new NetworkStats(TEST_START, 2) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12); + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12); final NetworkStats after = new NetworkStats(TEST_START, 2) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12); + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12); final NetworkStats result = after.subtract(before); @@ -234,12 +234,12 @@ public class NetworkStatsTest { @Test public void testSubtractIdenticalRows() throws Exception { final NetworkStats before = new NetworkStats(TEST_START, 2) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12); + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12); final NetworkStats after = new NetworkStats(TEST_START, 2) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1025L, 9L, 2L, 1L, 15) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 3L, 1L, 1028L, 9L, 20); + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1025L, 9L, 2L, 1L, 15) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 3L, 1L, 1028L, 9L, 20); final NetworkStats result = after.subtract(before); @@ -253,13 +253,13 @@ public class NetworkStatsTest { @Test public void testSubtractNewRows() throws Exception { final NetworkStats before = new NetworkStats(TEST_START, 2) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12); + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12); final NetworkStats after = new NetworkStats(TEST_START, 3) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12) - .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 20); + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12) + .insertEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 20); final NetworkStats result = after.subtract(before); @@ -275,11 +275,11 @@ public class NetworkStatsTest { @Test public void testSubtractMissingRows() throws Exception { final NetworkStats before = new NetworkStats(TEST_START, 2) - .addValues(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 1024L, 0L, 0L, 0L, 0) - .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2048L, 0L, 0L, 0L, 0); + .insertEntry(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 1024L, 0L, 0L, 0L, 0) + .insertEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2048L, 0L, 0L, 0L, 0); final NetworkStats after = new NetworkStats(TEST_START, 1) - .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2049L, 2L, 3L, 4L, 0); + .insertEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2049L, 2L, 3L, 4L, 0); final NetworkStats result = after.subtract(before); @@ -293,40 +293,40 @@ public class NetworkStatsTest { @Test public void testTotalBytes() throws Exception { final NetworkStats iface = new NetworkStats(TEST_START, 2) - .addValues(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 128L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 256L, 0L, 0L, 0L, 0L); + .insertEntry(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 128L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 256L, 0L, 0L, 0L, 0L); assertEquals(384L, iface.getTotalBytes()); final NetworkStats uidSet = new NetworkStats(TEST_START, 3) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_FOREGROUND, TAG_NONE, 32L, 0L, 0L, 0L, 0L); + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_FOREGROUND, TAG_NONE, 32L, 0L, 0L, 0L, 0L); assertEquals(96L, uidSet.getTotalBytes()); final NetworkStats uidTag = new NetworkStats(TEST_START, 6) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L); + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L); assertEquals(64L, uidTag.getTotalBytes()); final NetworkStats uidMetered = new NetworkStats(TEST_START, 3) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L); assertEquals(96L, uidMetered.getTotalBytes()); final NetworkStats uidRoaming = new NetworkStats(TEST_START, 3) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L); assertEquals(96L, uidRoaming.getTotalBytes()); } @@ -343,11 +343,11 @@ public class NetworkStatsTest { @Test public void testGroupedByIfaceAll() throws Exception { final NetworkStats uidStats = new NetworkStats(TEST_START, 3) - .addValues(IFACE_ALL, 100, SET_ALL, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(IFACE_ALL, 100, SET_ALL, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L) - .addValues(IFACE_ALL, 101, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_NO, + .insertEntry(IFACE_ALL, 101, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 128L, 8L, 0L, 2L, 20L) - .addValues(IFACE_ALL, 101, SET_ALL, TAG_NONE, METERED_NO, ROAMING_YES, + .insertEntry(IFACE_ALL, 101, SET_ALL, TAG_NONE, METERED_NO, ROAMING_YES, DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L); final NetworkStats grouped = uidStats.groupedByIface(); @@ -361,19 +361,19 @@ public class NetworkStatsTest { @Test public void testGroupedByIface() throws Exception { final NetworkStats uidStats = new NetworkStats(TEST_START, 7) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L) - .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 512L, 32L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 64L, 4L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 512L, 32L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 128L, 8L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO, + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, DEFAULT_NETWORK_YES, 128L, 8L, 0L, 0L, 0L); final NetworkStats grouped = uidStats.groupedByIface(); @@ -390,19 +390,19 @@ public class NetworkStatsTest { @Test public void testAddAllValues() { final NetworkStats first = new NetworkStats(TEST_START, 5) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES, + .insertEntry(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES, DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L); final NetworkStats second = new NetworkStats(TEST_START, 2) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES, + .insertEntry(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES, DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L); first.combineAllValues(second); @@ -421,19 +421,19 @@ public class NetworkStatsTest { @Test public void testGetTotal() { final NetworkStats stats = new NetworkStats(TEST_START, 7) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L) - .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 512L, 32L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 64L, 4L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 512L,32L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, 128L, 8L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L); assertValues(stats.getTotal(null), 1408L, 88L, 0L, 2L, 20L); @@ -459,7 +459,7 @@ public class NetworkStatsTest { assertEquals(0, after.size()); // Test 1 item stats. - before.addValues(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, 1L, 128L, 0L, 2L, 20L); + before.insertEntry(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, 1L, 128L, 0L, 2L, 20L); after = before.clone(); after.removeUids(new int[0]); assertEquals(1, after.size()); @@ -469,12 +469,12 @@ public class NetworkStatsTest { assertEquals(0, after.size()); // Append remaining test items. - before.addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 2L, 64L, 0L, 2L, 20L) - .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 4L, 32L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 16L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 8L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 4L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 64L, 2L, 0L, 0L, 0L); + before.insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 2L, 64L, 0L, 2L, 20L) + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 4L, 32L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 16L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 8L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 4L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 64L, 2L, 0L, 0L, 0L); assertEquals(7, before.size()); // Test remove with empty uid list. @@ -503,14 +503,61 @@ public class NetworkStatsTest { } @Test + public void testRemoveEmptyEntries() throws Exception { + // Test empty stats. + final NetworkStats statsEmpty = new NetworkStats(TEST_START, 3); + assertEquals(0, statsEmpty.removeEmptyEntries().size()); + + // Test stats with non-zero entry. + final NetworkStats statsNonZero = new NetworkStats(TEST_START, 1) + .insertEntry(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 1L, 128L, 0L, 2L, 20L); + assertEquals(1, statsNonZero.size()); + final NetworkStats expectedNonZero = statsNonZero.removeEmptyEntries(); + assertEquals(1, expectedNonZero.size()); + assertValues(expectedNonZero, 0, TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 1L, 128L, 0L, 2L, 20L); + + // Test stats with empty entry. + final NetworkStats statsZero = new NetworkStats(TEST_START, 1) + .insertEntry(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L); + assertEquals(1, statsZero.size()); + final NetworkStats expectedZero = statsZero.removeEmptyEntries(); + assertEquals(1, statsZero.size()); // Assert immutable. + assertEquals(0, expectedZero.size()); + + // Test stats with multiple entries. + final NetworkStats statsMultiple = new NetworkStats(TEST_START, 0) + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 2L, 64L, 0L, 2L, 20L) + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 4L, 32L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 0L, 8L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 4L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 0L, 2L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 0L, 0L, 1L); + assertEquals(9, statsMultiple.size()); + final NetworkStats expectedMultiple = statsMultiple.removeEmptyEntries(); + assertEquals(9, statsMultiple.size()); // Assert immutable. + assertEquals(7, expectedMultiple.size()); + assertValues(expectedMultiple.getTotalIncludingTags(null), 14L, 104L, 4L, 4L, 21L); + + // Test stats with multiple empty entries. + assertEquals(statsMultiple.size(), statsMultiple.subtract(statsMultiple).size()); + assertEquals(0, statsMultiple.subtract(statsMultiple).removeEmptyEntries().size()); + } + + @Test public void testClone() throws Exception { final NetworkStats original = new NetworkStats(TEST_START, 5) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L) - .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L); + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L) + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L); // make clone and mutate original final NetworkStats clone = original.clone(); - original.addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L); + original.insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L); assertEquals(3, original.size()); assertEquals(2, clone.size()); @@ -523,8 +570,8 @@ public class NetworkStatsTest { public void testAddWhenEmpty() throws Exception { final NetworkStats red = new NetworkStats(TEST_START, -1); final NetworkStats blue = new NetworkStats(TEST_START, 5) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L) - .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L); + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L) + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L); // We're mostly checking that we don't crash red.combineAllValues(blue); @@ -537,39 +584,39 @@ public class NetworkStatsTest { final String underlyingIface = "wlan0"; final int testTag1 = 8888; NetworkStats delta = new NetworkStats(TEST_START, 17) - .addValues(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 39605L, 46L, 12259L, 55L, 0L) - .addValues(tunIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L) - .addValues(tunIface, 10120, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 72667L, 197L, 43909L, 241L, 0L) - .addValues(tunIface, 10120, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 9297L, 17L, 4128L, 21L, 0L) - // VPN package also uses some traffic through unprotected network. - .addValues(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 4983L, 10L, 1801L, 12L, 0L) - .addValues(tunIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L) - // Tag entries - .addValues(tunIface, 10120, SET_DEFAULT, testTag1, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 21691L, 41L, 13820L, 51L, 0L) - .addValues(tunIface, 10120, SET_FOREGROUND, testTag1, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 1281L, 2L, 665L, 2L, 0L) - // Irrelevant entries - .addValues(TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 1685L, 5L, 2070L, 6L, 0L) - // Underlying Iface entries - .addValues(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 5178L, 8L, 2139L, 11L, 0L) - .addValues(underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L) - .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 149873L, 287L, 59217L /* smaller than sum(tun0) */, - 299L /* smaller than sum(tun0) */, 0L) - .addValues(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L); - - delta.migrateTun(tunUid, tunIface, new String[] {underlyingIface}); + .insertEntry(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 39605L, 46L, 12259L, 55L, 0L) + .insertEntry(tunIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L) + .insertEntry(tunIface, 10120, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 72667L, 197L, 43909L, 241L, 0L) + .insertEntry(tunIface, 10120, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 9297L, 17L, 4128L, 21L, 0L) + // VPN package also uses some traffic through unprotected network. + .insertEntry(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 4983L, 10L, 1801L, 12L, 0L) + .insertEntry(tunIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L) + // Tag entries + .insertEntry(tunIface, 10120, SET_DEFAULT, testTag1, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 21691L, 41L, 13820L, 51L, 0L) + .insertEntry(tunIface, 10120, SET_FOREGROUND, testTag1, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 1281L, 2L, 665L, 2L, 0L) + // Irrelevant entries + .insertEntry(TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 1685L, 5L, 2070L, 6L, 0L) + // Underlying Iface entries + .insertEntry(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 5178L, 8L, 2139L, 11L, 0L) + .insertEntry(underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L) + .insertEntry(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 149873L, 287L, 59217L /* smaller than sum(tun0) */, + 299L /* smaller than sum(tun0) */, 0L) + .insertEntry(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L); + + delta.migrateTun(tunUid, tunIface, new String[]{underlyingIface}); assertEquals(20, delta.size()); // tunIface and TEST_IFACE entries are not changed. @@ -634,21 +681,21 @@ public class NetworkStatsTest { final String tunIface = "tun0"; final String underlyingIface = "wlan0"; NetworkStats delta = new NetworkStats(TEST_START, 9) - // 2 different apps sent/receive data via tun0. - .addValues(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L) - .addValues(tunIface, 20100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 500L, 2L, 200L, 5L, 0L) - // VPN package resends data through the tunnel (with exaggerated overhead) - .addValues(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 240000, 100L, 120000L, 60L, 0L) - // 1 app already has some traffic on the underlying interface, the other doesn't yet - .addValues(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 1000L, 10L, 2000L, 20L, 0L) - // Traffic through the underlying interface via the vpn app. - // This test should redistribute this data correctly. - .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 75500L, 37L, 130000L, 70L, 0L); + // 2 different apps sent/receive data via tun0. + .insertEntry(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L) + .insertEntry(tunIface, 20100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 500L, 2L, 200L, 5L, 0L) + // VPN package resends data through the tunnel (with exaggerated overhead) + .insertEntry(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 240000, 100L, 120000L, 60L, 0L) + // 1 app already has some traffic on the underlying interface, the other doesn't yet + .insertEntry(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 1000L, 10L, 2000L, 20L, 0L) + // Traffic through the underlying interface via the vpn app. + // This test should redistribute this data correctly. + .insertEntry(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 75500L, 37L, 130000L, 70L, 0L); delta.migrateTun(tunUid, tunIface, new String[]{underlyingIface}); assertEquals(9, delta.size()); @@ -697,9 +744,9 @@ public class NetworkStatsTest { DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); NetworkStats stats = new NetworkStats(TEST_START, 3) - .addValues(entry1) - .addValues(entry2) - .addValues(entry3); + .insertEntry(entry1) + .insertEntry(entry2) + .insertEntry(entry3); stats.filter(UID_ALL, INTERFACES_ALL, TAG_ALL); assertEquals(3, stats.size()); @@ -724,9 +771,9 @@ public class NetworkStatsTest { DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); NetworkStats stats = new NetworkStats(TEST_START, 3) - .addValues(entry1) - .addValues(entry2) - .addValues(entry3); + .insertEntry(entry1) + .insertEntry(entry2) + .insertEntry(entry3); stats.filter(testUid, INTERFACES_ALL, TAG_ALL); assertEquals(2, stats.size()); @@ -755,10 +802,10 @@ public class NetworkStatsTest { DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); NetworkStats stats = new NetworkStats(TEST_START, 4) - .addValues(entry1) - .addValues(entry2) - .addValues(entry3) - .addValues(entry4); + .insertEntry(entry1) + .insertEntry(entry2) + .insertEntry(entry3) + .insertEntry(entry4); stats.filter(UID_ALL, new String[] { testIf1, testIf2 }, TAG_ALL); assertEquals(3, stats.size()); @@ -778,8 +825,8 @@ public class NetworkStatsTest { DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); NetworkStats stats = new NetworkStats(TEST_START, 3) - .addValues(entry1) - .addValues(entry2); + .insertEntry(entry1) + .insertEntry(entry2); stats.filter(UID_ALL, new String[] { }, TAG_ALL); assertEquals(0, stats.size()); @@ -802,9 +849,9 @@ public class NetworkStatsTest { DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); NetworkStats stats = new NetworkStats(TEST_START, 3) - .addValues(entry1) - .addValues(entry2) - .addValues(entry3); + .insertEntry(entry1) + .insertEntry(entry2) + .insertEntry(entry3); stats.filter(UID_ALL, INTERFACES_ALL, testTag); assertEquals(2, stats.size()); @@ -831,10 +878,10 @@ public class NetworkStatsTest { DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); NetworkStats stats = new NetworkStats(TEST_START, 4) - .addValues(entry1) - .addValues(entry2) - .addValues(entry3) - .addValues(entry4); + .insertEntry(entry1) + .insertEntry(entry2) + .insertEntry(entry3) + .insertEntry(entry4); stats.filterDebugEntries(); @@ -862,8 +909,8 @@ public class NetworkStatsTest { 13805 /* txPackets */, 0 /* operations */); - // Traffic measured for the root uid on the base interface if eBPF is in use. - final NetworkStats.Entry ebpfRootUidEntry = new NetworkStats.Entry( + // Traffic measured for the root uid on the base interface. + final NetworkStats.Entry rootUidEntry = new NetworkStats.Entry( baseIface, rootUid, SET_DEFAULT, TAG_NONE, 163577 /* rxBytes */, 187 /* rxPackets */, @@ -871,17 +918,6 @@ public class NetworkStatsTest { 97 /* txPackets */, 0 /* operations */); - // Traffic measured for the root uid on the base interface if xt_qtaguid is in use. - // Incorrectly includes appEntry's bytes and packets, plus IPv4-IPv6 translation - // overhead (20 bytes per packet), in rx direction. - final NetworkStats.Entry xtRootUidEntry = new NetworkStats.Entry( - baseIface, rootUid, SET_DEFAULT, TAG_NONE, - 31113087 /* rxBytes */, - 22588 /* rxPackets */, - 17607 /* txBytes */, - 97 /* txPackets */, - 0 /* operations */); - final NetworkStats.Entry otherEntry = new NetworkStats.Entry( otherIface, appUid, SET_DEFAULT, TAG_NONE, 2600 /* rxBytes */, @@ -890,21 +926,14 @@ public class NetworkStatsTest { 3 /* txPackets */, 0 /* operations */); - final NetworkStats statsXt = new NetworkStats(TEST_START, 3) - .addValues(appEntry) - .addValues(xtRootUidEntry) - .addValues(otherEntry); - - final NetworkStats statsEbpf = new NetworkStats(TEST_START, 3) - .addValues(appEntry) - .addValues(ebpfRootUidEntry) - .addValues(otherEntry); + final NetworkStats stats = new NetworkStats(TEST_START, 3) + .insertEntry(appEntry) + .insertEntry(rootUidEntry) + .insertEntry(otherEntry); - statsXt.apply464xlatAdjustments(stackedIface, false); - statsEbpf.apply464xlatAdjustments(stackedIface, true); + stats.apply464xlatAdjustments(stackedIface); - assertEquals(3, statsXt.size()); - assertEquals(3, statsEbpf.size()); + assertEquals(3, stats.size()); final NetworkStats.Entry expectedAppUid = new NetworkStats.Entry( v4Iface, appUid, SET_DEFAULT, TAG_NONE, 30949510, @@ -919,12 +948,9 @@ public class NetworkStatsTest { 17607, 97, 0); - assertEquals(expectedAppUid, statsXt.getValues(0, null)); - assertEquals(expectedRootUid, statsXt.getValues(1, null)); - assertEquals(otherEntry, statsXt.getValues(2, null)); - assertEquals(expectedAppUid, statsEbpf.getValues(0, null)); - assertEquals(expectedRootUid, statsEbpf.getValues(1, null)); - assertEquals(otherEntry, statsEbpf.getValues(2, null)); + assertEquals(expectedAppUid, stats.getValues(0, null)); + assertEquals(expectedRootUid, stats.getValues(1, null)); + assertEquals(otherEntry, stats.getValues(2, null)); } @Test @@ -945,11 +971,11 @@ public class NetworkStatsTest { 0 /* operations */); NetworkStats stats = new NetworkStats(TEST_START, 2) - .addValues(firstEntry) - .addValues(secondEntry); + .insertEntry(firstEntry) + .insertEntry(secondEntry); // Empty map: no adjustment - stats.apply464xlatAdjustments(new ArrayMap<>(), false); + stats.apply464xlatAdjustments(new ArrayMap<>()); assertEquals(2, stats.size()); assertEquals(firstEntry, stats.getValues(0, null)); diff --git a/tests/net/java/android/net/NetworkTemplateTest.kt b/tests/net/java/android/net/NetworkTemplateTest.kt new file mode 100644 index 000000000000..9ba56e44fe88 --- /dev/null +++ b/tests/net/java/android/net/NetworkTemplateTest.kt @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net + +import android.content.Context +import android.net.ConnectivityManager.TYPE_MOBILE +import android.net.ConnectivityManager.TYPE_WIFI +import android.net.NetworkIdentity.SUBTYPE_COMBINED +import android.net.NetworkIdentity.buildNetworkIdentity +import android.net.NetworkStats.DEFAULT_NETWORK_ALL +import android.net.NetworkStats.METERED_ALL +import android.net.NetworkStats.ROAMING_ALL +import android.net.NetworkTemplate.MATCH_MOBILE +import android.net.NetworkTemplate.MATCH_WIFI +import android.net.NetworkTemplate.NETWORK_TYPE_5G_NSA +import android.net.NetworkTemplate.NETWORK_TYPE_ALL +import android.net.NetworkTemplate.buildTemplateMobileWithRatType +import android.telephony.TelephonyManager +import com.android.testutils.assertParcelSane +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock +import org.mockito.MockitoAnnotations +import kotlin.test.assertFalse +import kotlin.test.assertNotEquals +import kotlin.test.assertTrue + +private const val TEST_IMSI1 = "imsi1" +private const val TEST_IMSI2 = "imsi2" +private const val TEST_SSID1 = "ssid1" + +@RunWith(JUnit4::class) +class NetworkTemplateTest { + private val mockContext = mock(Context::class.java) + + private fun buildMobileNetworkState(subscriberId: String): NetworkState = + buildNetworkState(TYPE_MOBILE, subscriberId = subscriberId) + private fun buildWifiNetworkState(ssid: String): NetworkState = + buildNetworkState(TYPE_WIFI, ssid = ssid) + + private fun buildNetworkState( + type: Int, + subscriberId: String? = null, + ssid: String? = null + ): NetworkState { + val info = mock(NetworkInfo::class.java) + doReturn(type).`when`(info).type + doReturn(NetworkInfo.State.CONNECTED).`when`(info).state + val lp = LinkProperties() + val caps = NetworkCapabilities().apply { + setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, false) + setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true) + } + return NetworkState(info, lp, caps, mock(Network::class.java), subscriberId, ssid) + } + + private fun NetworkTemplate.assertMatches(ident: NetworkIdentity) = + assertTrue(matches(ident), "$this does not match $ident") + + private fun NetworkTemplate.assertDoesNotMatch(ident: NetworkIdentity) = + assertFalse(matches(ident), "$this should match $ident") + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + } + + @Test + fun testRatTypeGroupMatches() { + val stateMobile = buildMobileNetworkState(TEST_IMSI1) + // Build UMTS template that matches mobile identities with RAT in the same + // group with any IMSI. See {@link NetworkTemplate#getCollapsedRatType}. + val templateUmts = buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_UMTS) + // Build normal template that matches mobile identities with any RAT and IMSI. + val templateAll = buildTemplateMobileWithRatType(null, NETWORK_TYPE_ALL) + // Build template with UNKNOWN RAT that matches mobile identities with RAT that + // cannot be determined. + val templateUnknown = + buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_UNKNOWN) + + val identUmts = buildNetworkIdentity( + mockContext, stateMobile, false, TelephonyManager.NETWORK_TYPE_UMTS) + val identHsdpa = buildNetworkIdentity( + mockContext, stateMobile, false, TelephonyManager.NETWORK_TYPE_HSDPA) + val identLte = buildNetworkIdentity( + mockContext, stateMobile, false, TelephonyManager.NETWORK_TYPE_LTE) + val identCombined = buildNetworkIdentity( + mockContext, stateMobile, false, SUBTYPE_COMBINED) + val identImsi2 = buildNetworkIdentity(mockContext, buildMobileNetworkState(TEST_IMSI2), + false, TelephonyManager.NETWORK_TYPE_UMTS) + val identWifi = buildNetworkIdentity( + mockContext, buildWifiNetworkState(TEST_SSID1), true, 0) + + // Assert that identity with the same RAT matches. + templateUmts.assertMatches(identUmts) + templateAll.assertMatches(identUmts) + templateUnknown.assertDoesNotMatch(identUmts) + // Assert that identity with the RAT within the same group matches. + templateUmts.assertMatches(identHsdpa) + templateAll.assertMatches(identHsdpa) + templateUnknown.assertDoesNotMatch(identHsdpa) + // Assert that identity with the RAT out of the same group only matches template with + // NETWORK_TYPE_ALL. + templateUmts.assertDoesNotMatch(identLte) + templateAll.assertMatches(identLte) + templateUnknown.assertDoesNotMatch(identLte) + // Assert that identity with combined RAT only matches with template with NETWORK_TYPE_ALL + // and NETWORK_TYPE_UNKNOWN. + templateUmts.assertDoesNotMatch(identCombined) + templateAll.assertMatches(identCombined) + templateUnknown.assertMatches(identCombined) + // Assert that identity with different IMSI matches. + templateUmts.assertMatches(identImsi2) + templateAll.assertMatches(identImsi2) + templateUnknown.assertDoesNotMatch(identImsi2) + // Assert that wifi identity does not match. + templateUmts.assertDoesNotMatch(identWifi) + templateAll.assertDoesNotMatch(identWifi) + templateUnknown.assertDoesNotMatch(identWifi) + } + + @Test + fun testParcelUnparcel() { + val templateMobile = NetworkTemplate(MATCH_MOBILE, TEST_IMSI1, null, null, METERED_ALL, + ROAMING_ALL, DEFAULT_NETWORK_ALL, TelephonyManager.NETWORK_TYPE_LTE) + val templateWifi = NetworkTemplate(MATCH_WIFI, null, null, TEST_SSID1, METERED_ALL, + ROAMING_ALL, DEFAULT_NETWORK_ALL, 0) + assertParcelSane(templateMobile, 8) + assertParcelSane(templateWifi, 8) + } + + // Verify NETWORK_TYPE_* constants in NetworkTemplate do not conflict with + // TelephonyManager#NETWORK_TYPE_* constants. + @Test + fun testNetworkTypeConstants() { + for (ratType in TelephonyManager.getAllNetworkTypes()) { + assertNotEquals(NETWORK_TYPE_ALL, ratType) + assertNotEquals(NETWORK_TYPE_5G_NSA, ratType) + } + } +} diff --git a/tests/net/java/android/net/NetworkUtilsTest.java b/tests/net/java/android/net/NetworkUtilsTest.java index 7748288aeb05..3158cc8637e4 100644 --- a/tests/net/java/android/net/NetworkUtilsTest.java +++ b/tests/net/java/android/net/NetworkUtilsTest.java @@ -16,10 +16,24 @@ package android.net; +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; +import static android.system.OsConstants.AF_UNIX; +import static android.system.OsConstants.EPERM; +import static android.system.OsConstants.SOCK_DGRAM; +import static android.system.OsConstants.SOCK_STREAM; + import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.fail; + +import android.system.ErrnoException; +import android.system.Os; + import androidx.test.runner.AndroidJUnit4; +import libcore.io.IoUtils; + import org.junit.Test; import org.junit.runner.RunWith; @@ -125,4 +139,50 @@ public class NetworkUtilsTest { assertEquals(BigInteger.valueOf(7l - 4 + 4 + 16 + 65536), NetworkUtils.routedIPv6AddressCount(set)); } + + private static void expectSocketSuccess(String msg, int domain, int type) { + try { + IoUtils.closeQuietly(Os.socket(domain, type, 0)); + } catch (ErrnoException e) { + fail(msg + e.getMessage()); + } + } + + private static void expectSocketPemissionError(String msg, int domain, int type) { + try { + IoUtils.closeQuietly(Os.socket(domain, type, 0)); + fail(msg); + } catch (ErrnoException e) { + assertEquals(msg, e.errno, EPERM); + } + } + + private static void expectHasNetworking() { + expectSocketSuccess("Creating a UNIX socket should not have thrown ErrnoException", + AF_UNIX, SOCK_STREAM); + expectSocketSuccess("Creating a AF_INET socket shouldn't have thrown ErrnoException", + AF_INET, SOCK_DGRAM); + expectSocketSuccess("Creating a AF_INET6 socket shouldn't have thrown ErrnoException", + AF_INET6, SOCK_DGRAM); + } + + private static void expectNoNetworking() { + expectSocketSuccess("Creating a UNIX socket should not have thrown ErrnoException", + AF_UNIX, SOCK_STREAM); + expectSocketPemissionError( + "Creating a AF_INET socket should have thrown ErrnoException(EPERM)", + AF_INET, SOCK_DGRAM); + expectSocketPemissionError( + "Creating a AF_INET6 socket should have thrown ErrnoException(EPERM)", + AF_INET6, SOCK_DGRAM); + } + + @Test + public void testSetAllowNetworkingForProcess() { + expectHasNetworking(); + NetworkUtils.setAllowNetworkingForProcess(false); + expectNoNetworking(); + NetworkUtils.setAllowNetworkingForProcess(true); + expectHasNetworking(); + } } diff --git a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java index 583d3fd536aa..cea8c5713a6b 100644 --- a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java +++ b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java @@ -16,14 +16,12 @@ package android.net; +import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless; + import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; -import android.net.SocketKeepalive.InvalidPacketException; - -import com.android.internal.util.TestUtils; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -68,10 +66,10 @@ public final class TcpKeepalivePacketDataTest { fail("InvalidPacketException: " + e); } - assertEquals(InetAddress.getByAddress(testInfo.srcAddress), resultData.srcAddress); - assertEquals(InetAddress.getByAddress(testInfo.dstAddress), resultData.dstAddress); - assertEquals(testInfo.srcPort, resultData.srcPort); - assertEquals(testInfo.dstPort, resultData.dstPort); + assertEquals(InetAddress.getByAddress(testInfo.srcAddress), resultData.getSrcAddress()); + assertEquals(InetAddress.getByAddress(testInfo.dstAddress), resultData.getDstAddress()); + assertEquals(testInfo.srcPort, resultData.getSrcPort()); + assertEquals(testInfo.dstPort, resultData.getDstPort()); assertEquals(testInfo.seq, resultData.tcpSeq); assertEquals(testInfo.ack, resultData.tcpAck); assertEquals(testInfo.rcvWnd, resultData.tcpWnd); @@ -79,7 +77,7 @@ public final class TcpKeepalivePacketDataTest { assertEquals(testInfo.tos, resultData.ipTos); assertEquals(testInfo.ttl, resultData.ipTtl); - TestUtils.assertParcelingIsLossless(resultData); + assertParcelingIsLossless(resultData); final byte[] packet = resultData.getPacket(); // IP version and IHL diff --git a/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java b/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java new file mode 100644 index 000000000000..efb92033df1e --- /dev/null +++ b/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static com.android.testutils.ParcelUtilsKt.assertParcelSane; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.net.wifi.WifiNetworkSpecifier; +import android.telephony.SubscriptionManager; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; + +/** + * Unit test for {@link android.net.TelephonyNetworkSpecifier}. + */ +@SmallTest +public class TelephonyNetworkSpecifierTest { + private static final int TEST_SUBID = 5; + private static final String TEST_SSID = "Test123"; + + /** + * Validate that IllegalArgumentException will be thrown if build TelephonyNetworkSpecifier + * without calling {@link TelephonyNetworkSpecifier.Builder#setSubscriptionId(int)}. + */ + @Test + public void testBuilderBuildWithDefault() { + try { + new TelephonyNetworkSpecifier.Builder().build(); + } catch (IllegalArgumentException iae) { + // expected, test pass + } + } + + /** + * Validate that no exception will be thrown even if pass invalid subscription id to + * {@link TelephonyNetworkSpecifier.Builder#setSubscriptionId(int)}. + */ + @Test + public void testBuilderBuildWithInvalidSubId() { + TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder() + .setSubscriptionId(SubscriptionManager.INVALID_SUBSCRIPTION_ID) + .build(); + assertEquals(specifier.getSubscriptionId(), SubscriptionManager.INVALID_SUBSCRIPTION_ID); + } + + /** + * Validate the correctness of TelephonyNetworkSpecifier when provide valid subId. + */ + @Test + public void testBuilderBuildWithValidSubId() { + final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder() + .setSubscriptionId(TEST_SUBID) + .build(); + assertEquals(TEST_SUBID, specifier.getSubscriptionId()); + } + + /** + * Validate that parcel marshalling/unmarshalling works. + */ + @Test + public void testParcel() { + TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder() + .setSubscriptionId(TEST_SUBID) + .build(); + assertParcelSane(specifier, 1 /* fieldCount */); + } + + /** + * Validate the behavior of method canBeSatisfiedBy(). + */ + @Test + public void testCanBeSatisfiedBy() { + final TelephonyNetworkSpecifier tns1 = new TelephonyNetworkSpecifier.Builder() + .setSubscriptionId(TEST_SUBID) + .build(); + final TelephonyNetworkSpecifier tns2 = new TelephonyNetworkSpecifier.Builder() + .setSubscriptionId(TEST_SUBID) + .build(); + final WifiNetworkSpecifier wns = new WifiNetworkSpecifier.Builder() + .setSsid(TEST_SSID) + .build(); + final MatchAllNetworkSpecifier mans = new MatchAllNetworkSpecifier(); + + // Test equality + assertEquals(tns1, tns2); + assertTrue(tns1.canBeSatisfiedBy(tns1)); + assertTrue(tns1.canBeSatisfiedBy(tns2)); + + // Test other edge cases. + assertFalse(tns1.canBeSatisfiedBy(null)); + assertFalse(tns1.canBeSatisfiedBy(wns)); + assertTrue(tns1.canBeSatisfiedBy(mans)); + } +} diff --git a/tests/net/java/android/net/VpnManagerTest.java b/tests/net/java/android/net/VpnManagerTest.java new file mode 100644 index 000000000000..95a794235a2e --- /dev/null +++ b/tests/net/java/android/net/VpnManagerTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; +import android.content.Intent; +import android.test.mock.MockContext; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.net.VpnProfile; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit tests for {@link VpnManager}. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class VpnManagerTest { + private static final String PKG_NAME = "fooPackage"; + + private static final String SESSION_NAME_STRING = "testSession"; + private static final String SERVER_ADDR_STRING = "1.2.3.4"; + private static final String IDENTITY_STRING = "Identity"; + private static final byte[] PSK_BYTES = "preSharedKey".getBytes(); + + private IConnectivityManager mMockCs; + private VpnManager mVpnManager; + private final MockContext mMockContext = + new MockContext() { + @Override + public String getOpPackageName() { + return PKG_NAME; + } + }; + + @Before + public void setUp() throws Exception { + mMockCs = mock(IConnectivityManager.class); + mVpnManager = new VpnManager(mMockContext, mMockCs); + } + + @Test + public void testProvisionVpnProfilePreconsented() throws Exception { + final PlatformVpnProfile profile = getPlatformVpnProfile(); + when(mMockCs.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))).thenReturn(true); + + // Expect there to be no intent returned, as consent has already been granted. + assertNull(mVpnManager.provisionVpnProfile(profile)); + verify(mMockCs).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME)); + } + + @Test + public void testProvisionVpnProfileNeedsConsent() throws Exception { + final PlatformVpnProfile profile = getPlatformVpnProfile(); + when(mMockCs.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))).thenReturn(false); + + // Expect intent to be returned, as consent has not already been granted. + final Intent intent = mVpnManager.provisionVpnProfile(profile); + assertNotNull(intent); + + final ComponentName expectedComponentName = + ComponentName.unflattenFromString( + "com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog"); + assertEquals(expectedComponentName, intent.getComponent()); + verify(mMockCs).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME)); + } + + @Test + public void testDeleteProvisionedVpnProfile() throws Exception { + mVpnManager.deleteProvisionedVpnProfile(); + verify(mMockCs).deleteVpnProfile(eq(PKG_NAME)); + } + + @Test + public void testStartProvisionedVpnProfile() throws Exception { + mVpnManager.startProvisionedVpnProfile(); + verify(mMockCs).startVpnProfile(eq(PKG_NAME)); + } + + @Test + public void testStopProvisionedVpnProfile() throws Exception { + mVpnManager.stopProvisionedVpnProfile(); + verify(mMockCs).stopVpnProfile(eq(PKG_NAME)); + } + + private Ikev2VpnProfile getPlatformVpnProfile() throws Exception { + return new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING) + .setBypassable(true) + .setMaxMtu(1300) + .setMetered(true) + .setAuthPsk(PSK_BYTES) + .build(); + } +} diff --git a/tests/net/java/android/net/dhcp/DhcpServingParamsParcelExtTest.java b/tests/net/java/android/net/dhcp/DhcpServingParamsParcelExtTest.java deleted file mode 100644 index e01ac7f08c2f..000000000000 --- a/tests/net/java/android/net/dhcp/DhcpServingParamsParcelExtTest.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.dhcp; - -import static android.net.InetAddresses.parseNumericAddress; - -import static com.google.android.collect.Sets.newHashSet; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.net.LinkAddress; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.net.Inet4Address; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class DhcpServingParamsParcelExtTest { - private static final Inet4Address TEST_ADDRESS = inet4Addr("192.168.0.123"); - private static final int TEST_ADDRESS_PARCELED = 0xc0a8007b; - private static final int TEST_PREFIX_LENGTH = 17; - private static final int TEST_LEASE_TIME_SECS = 120; - private static final int TEST_MTU = 1000; - private static final Set<Inet4Address> TEST_ADDRESS_SET = - newHashSet(inet4Addr("192.168.1.123"), inet4Addr("192.168.1.124")); - private static final Set<Integer> TEST_ADDRESS_SET_PARCELED = - newHashSet(0xc0a8017b, 0xc0a8017c); - - private DhcpServingParamsParcelExt mParcel; - - @Before - public void setUp() { - mParcel = new DhcpServingParamsParcelExt(); - } - - @Test - public void testSetServerAddr() { - mParcel.setServerAddr(new LinkAddress(TEST_ADDRESS, TEST_PREFIX_LENGTH)); - - assertEquals(TEST_ADDRESS_PARCELED, mParcel.serverAddr); - assertEquals(TEST_PREFIX_LENGTH, mParcel.serverAddrPrefixLength); - } - - @Test - public void testSetDefaultRouters() { - mParcel.setDefaultRouters(TEST_ADDRESS_SET); - assertEquals(TEST_ADDRESS_SET_PARCELED, asSet(mParcel.defaultRouters)); - } - - @Test - public void testSetDnsServers() { - mParcel.setDnsServers(TEST_ADDRESS_SET); - assertEquals(TEST_ADDRESS_SET_PARCELED, asSet(mParcel.dnsServers)); - } - - @Test - public void testSetExcludedAddrs() { - mParcel.setExcludedAddrs(TEST_ADDRESS_SET); - assertEquals(TEST_ADDRESS_SET_PARCELED, asSet(mParcel.excludedAddrs)); - } - - @Test - public void testSetDhcpLeaseTimeSecs() { - mParcel.setDhcpLeaseTimeSecs(TEST_LEASE_TIME_SECS); - assertEquals(TEST_LEASE_TIME_SECS, mParcel.dhcpLeaseTimeSecs); - } - - @Test - public void testSetLinkMtu() { - mParcel.setLinkMtu(TEST_MTU); - assertEquals(TEST_MTU, mParcel.linkMtu); - } - - @Test - public void testSetMetered() { - mParcel.setMetered(true); - assertTrue(mParcel.metered); - mParcel.setMetered(false); - assertFalse(mParcel.metered); - } - - private static Inet4Address inet4Addr(String addr) { - return (Inet4Address) parseNumericAddress(addr); - } - - private static Set<Integer> asSet(int[] ints) { - return IntStream.of(ints).boxed().collect(Collectors.toSet()); - } -} diff --git a/tests/net/java/android/net/ip/InterfaceControllerTest.java b/tests/net/java/android/net/ip/InterfaceControllerTest.java deleted file mode 100644 index 7a56b3aafee7..000000000000 --- a/tests/net/java/android/net/ip/InterfaceControllerTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.ip; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import android.net.INetd; -import android.net.InetAddresses; -import android.net.InterfaceConfigurationParcel; -import android.net.LinkAddress; -import android.net.util.SharedLog; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class InterfaceControllerTest { - private static final String TEST_IFACE = "testif"; - private static final String TEST_IPV4_ADDR = "192.168.123.28"; - private static final int TEST_PREFIXLENGTH = 31; - - @Mock private INetd mNetd; - @Mock private SharedLog mLog; - @Captor private ArgumentCaptor<InterfaceConfigurationParcel> mConfigCaptor; - - private InterfaceController mController; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - mController = new InterfaceController(TEST_IFACE, mNetd, mLog); - - doNothing().when(mNetd).interfaceSetCfg(mConfigCaptor.capture()); - } - - @Test - public void testSetIPv4Address() throws Exception { - mController.setIPv4Address( - new LinkAddress(InetAddresses.parseNumericAddress(TEST_IPV4_ADDR), - TEST_PREFIXLENGTH)); - verify(mNetd, times(1)).interfaceSetCfg(any()); - final InterfaceConfigurationParcel parcel = mConfigCaptor.getValue(); - assertEquals(TEST_IFACE, parcel.ifName); - assertEquals(TEST_IPV4_ADDR, parcel.ipv4Addr); - assertEquals(TEST_PREFIXLENGTH, parcel.prefixLength); - assertEquals("", parcel.hwAddr); - assertArrayEquals(new String[0], parcel.flags); - } - - @Test - public void testClearIPv4Address() throws Exception { - mController.clearIPv4Address(); - verify(mNetd, times(1)).interfaceSetCfg(any()); - final InterfaceConfigurationParcel parcel = mConfigCaptor.getValue(); - assertEquals(TEST_IFACE, parcel.ifName); - assertEquals("0.0.0.0", parcel.ipv4Addr); - assertEquals(0, parcel.prefixLength); - assertEquals("", parcel.hwAddr); - assertArrayEquals(new String[0], parcel.flags); - } -} diff --git a/tests/net/java/android/net/ip/IpServerTest.java b/tests/net/java/android/net/ip/IpServerTest.java deleted file mode 100644 index 05912e85426a..000000000000 --- a/tests/net/java/android/net/ip/IpServerTest.java +++ /dev/null @@ -1,499 +0,0 @@ -/* - * 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. - * 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 static android.net.ConnectivityManager.TETHERING_BLUETOOTH; -import static android.net.ConnectivityManager.TETHERING_USB; -import static android.net.ConnectivityManager.TETHERING_WIFI; -import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR; -import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; -import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR; -import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; -import static android.net.ip.IpServer.STATE_AVAILABLE; -import static android.net.ip.IpServer.STATE_TETHERED; -import static android.net.ip.IpServer.STATE_UNAVAILABLE; -import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH; - -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.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import android.net.INetd; -import android.net.INetworkStatsService; -import android.net.InterfaceConfiguration; -import android.net.IpPrefix; -import android.net.LinkAddress; -import android.net.LinkProperties; -import android.net.MacAddress; -import android.net.RouteInfo; -import android.net.dhcp.DhcpServingParamsParcel; -import android.net.dhcp.IDhcpServer; -import android.net.dhcp.IDhcpServerCallbacks; -import android.net.util.InterfaceParams; -import android.net.util.InterfaceSet; -import android.net.util.SharedLog; -import android.os.INetworkManagementService; -import android.os.RemoteException; -import android.os.test.TestLooper; -import android.text.TextUtils; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.net.Inet4Address; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class IpServerTest { - private static final String IFACE_NAME = "testnet1"; - private static final String UPSTREAM_IFACE = "upstream0"; - private static final String UPSTREAM_IFACE2 = "upstream1"; - private static final int DHCP_LEASE_TIME_SECS = 3600; - - private static final InterfaceParams TEST_IFACE_PARAMS = new InterfaceParams( - IFACE_NAME, 42 /* index */, MacAddress.ALL_ZEROS_ADDRESS, 1500 /* defaultMtu */); - - private static final int MAKE_DHCPSERVER_TIMEOUT_MS = 1000; - - @Mock private INetworkManagementService mNMService; - @Mock private INetd mNetd; - @Mock private INetworkStatsService mStatsService; - @Mock private IpServer.Callback mCallback; - @Mock private InterfaceConfiguration mInterfaceConfiguration; - @Mock private SharedLog mSharedLog; - @Mock private IDhcpServer mDhcpServer; - @Mock private RouterAdvertisementDaemon mRaDaemon; - @Mock private IpServer.Dependencies mDependencies; - - @Captor private ArgumentCaptor<DhcpServingParamsParcel> mDhcpParamsCaptor; - - private final TestLooper mLooper = new TestLooper(); - private final ArgumentCaptor<LinkProperties> mLinkPropertiesCaptor = - ArgumentCaptor.forClass(LinkProperties.class); - private IpServer mIpServer; - - private void initStateMachine(int interfaceType) throws Exception { - initStateMachine(interfaceType, false /* usingLegacyDhcp */); - } - - private void initStateMachine(int interfaceType, boolean usingLegacyDhcp) throws Exception { - doAnswer(inv -> { - final IDhcpServerCallbacks cb = inv.getArgument(2); - new Thread(() -> { - try { - cb.onDhcpServerCreated(STATUS_SUCCESS, mDhcpServer); - } catch (RemoteException e) { - fail(e.getMessage()); - } - }).run(); - return null; - }).when(mDependencies).makeDhcpServer(any(), mDhcpParamsCaptor.capture(), any()); - when(mDependencies.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon); - when(mDependencies.getInterfaceParams(IFACE_NAME)).thenReturn(TEST_IFACE_PARAMS); - when(mDependencies.getNetdService()).thenReturn(mNetd); - - mIpServer = new IpServer( - IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, - mNMService, mStatsService, mCallback, usingLegacyDhcp, mDependencies); - mIpServer.start(); - // Starting the state machine always puts us in a consistent state and notifies - // the rest of the world that we've changed from an unknown to available state. - mLooper.dispatchAll(); - reset(mNMService, mStatsService, mCallback); - when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration); - - when(mRaDaemon.start()).thenReturn(true); - } - - private void initTetheredStateMachine(int interfaceType, String upstreamIface) - throws Exception { - initTetheredStateMachine(interfaceType, upstreamIface, false); - } - - private void initTetheredStateMachine(int interfaceType, String upstreamIface, - boolean usingLegacyDhcp) throws Exception { - initStateMachine(interfaceType, usingLegacyDhcp); - dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED); - if (upstreamIface != null) { - dispatchTetherConnectionChanged(upstreamIface); - } - reset(mNMService, mStatsService, mCallback); - when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration); - } - - @Before public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - when(mSharedLog.forSubComponent(anyString())).thenReturn(mSharedLog); - } - - @Test - public void startsOutAvailable() { - mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), - TETHERING_BLUETOOTH, mSharedLog, mNMService, mStatsService, mCallback, - false /* usingLegacyDhcp */, mDependencies); - mIpServer.start(); - mLooper.dispatchAll(); - verify(mCallback).updateInterfaceState( - mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR); - verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class)); - verifyNoMoreInteractions(mCallback, mNMService, mStatsService); - } - - @Test - public void shouldDoNothingUntilRequested() throws Exception { - initStateMachine(TETHERING_BLUETOOTH); - final int [] NOOP_COMMANDS = { - IpServer.CMD_TETHER_UNREQUESTED, - IpServer.CMD_IP_FORWARDING_ENABLE_ERROR, - IpServer.CMD_IP_FORWARDING_DISABLE_ERROR, - IpServer.CMD_START_TETHERING_ERROR, - IpServer.CMD_STOP_TETHERING_ERROR, - IpServer.CMD_SET_DNS_FORWARDERS_ERROR, - IpServer.CMD_TETHER_CONNECTION_CHANGED - }; - for (int command : NOOP_COMMANDS) { - // None of these commands should trigger us to request action from - // the rest of the system. - dispatchCommand(command); - verifyNoMoreInteractions(mNMService, mStatsService, mCallback); - } - } - - @Test - public void handlesImmediateInterfaceDown() throws Exception { - initStateMachine(TETHERING_BLUETOOTH); - - dispatchCommand(IpServer.CMD_INTERFACE_DOWN); - verify(mCallback).updateInterfaceState( - mIpServer, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR); - verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class)); - verifyNoMoreInteractions(mNMService, mStatsService, mCallback); - } - - @Test - public void canBeTethered() throws Exception { - initStateMachine(TETHERING_BLUETOOTH); - - dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED); - InOrder inOrder = inOrder(mCallback, mNMService); - inOrder.verify(mNMService).tetherInterface(IFACE_NAME); - inOrder.verify(mCallback).updateInterfaceState( - mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR); - inOrder.verify(mCallback).updateLinkProperties( - eq(mIpServer), any(LinkProperties.class)); - verifyNoMoreInteractions(mNMService, mStatsService, mCallback); - } - - @Test - public void canUnrequestTethering() throws Exception { - initTetheredStateMachine(TETHERING_BLUETOOTH, null); - - dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED); - InOrder inOrder = inOrder(mNMService, mNetd, mStatsService, mCallback); - inOrder.verify(mNMService).untetherInterface(IFACE_NAME); - inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName))); - inOrder.verify(mCallback).updateInterfaceState( - mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR); - inOrder.verify(mCallback).updateLinkProperties( - eq(mIpServer), any(LinkProperties.class)); - verifyNoMoreInteractions(mNMService, mStatsService, mCallback); - } - - @Test - public void canBeTetheredAsUsb() throws Exception { - initStateMachine(TETHERING_USB); - - dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED); - InOrder inOrder = inOrder(mCallback, mNMService); - inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME); - inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration); - inOrder.verify(mNMService).tetherInterface(IFACE_NAME); - inOrder.verify(mCallback).updateInterfaceState( - mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR); - inOrder.verify(mCallback).updateLinkProperties( - eq(mIpServer), mLinkPropertiesCaptor.capture()); - assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue()); - verifyNoMoreInteractions(mNMService, mStatsService, mCallback); - } - - @Test - public void handlesFirstUpstreamChange() throws Exception { - initTetheredStateMachine(TETHERING_BLUETOOTH, null); - - // Telling the state machine about its upstream interface triggers - // a little more configuration. - dispatchTetherConnectionChanged(UPSTREAM_IFACE); - InOrder inOrder = inOrder(mNMService); - inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); - verifyNoMoreInteractions(mNMService, mStatsService, mCallback); - } - - @Test - public void handlesChangingUpstream() throws Exception { - initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE); - - dispatchTetherConnectionChanged(UPSTREAM_IFACE2); - InOrder inOrder = inOrder(mNMService, mStatsService); - inOrder.verify(mStatsService).forceUpdate(); - inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2); - inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2); - verifyNoMoreInteractions(mNMService, mStatsService, mCallback); - } - - @Test - public void handlesChangingUpstreamNatFailure() throws Exception { - initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE); - - doThrow(RemoteException.class).when(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2); - - dispatchTetherConnectionChanged(UPSTREAM_IFACE2); - InOrder inOrder = inOrder(mNMService, mStatsService); - inOrder.verify(mStatsService).forceUpdate(); - inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2); - inOrder.verify(mStatsService).forceUpdate(); - inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2); - inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2); - } - - @Test - public void handlesChangingUpstreamInterfaceForwardingFailure() throws Exception { - initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE); - - doThrow(RemoteException.class).when(mNMService).startInterfaceForwarding( - IFACE_NAME, UPSTREAM_IFACE2); - - dispatchTetherConnectionChanged(UPSTREAM_IFACE2); - InOrder inOrder = inOrder(mNMService, mStatsService); - inOrder.verify(mStatsService).forceUpdate(); - inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2); - inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2); - inOrder.verify(mStatsService).forceUpdate(); - inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2); - inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2); - } - - @Test - public void canUnrequestTetheringWithUpstream() throws Exception { - initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE); - - dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED); - InOrder inOrder = inOrder(mNMService, mNetd, mStatsService, mCallback); - inOrder.verify(mStatsService).forceUpdate(); - inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mNMService).untetherInterface(IFACE_NAME); - inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName))); - inOrder.verify(mCallback).updateInterfaceState( - mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR); - inOrder.verify(mCallback).updateLinkProperties( - eq(mIpServer), any(LinkProperties.class)); - verifyNoMoreInteractions(mNMService, mStatsService, mCallback); - } - - @Test - public void interfaceDownLeadsToUnavailable() throws Exception { - for (boolean shouldThrow : new boolean[]{true, false}) { - initTetheredStateMachine(TETHERING_USB, null); - - if (shouldThrow) { - doThrow(RemoteException.class).when(mNMService).untetherInterface(IFACE_NAME); - } - dispatchCommand(IpServer.CMD_INTERFACE_DOWN); - InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mCallback); - usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown(); - usbTeardownOrder.verify(mNMService).setInterfaceConfig( - IFACE_NAME, mInterfaceConfiguration); - usbTeardownOrder.verify(mCallback).updateInterfaceState( - mIpServer, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR); - usbTeardownOrder.verify(mCallback).updateLinkProperties( - eq(mIpServer), mLinkPropertiesCaptor.capture()); - assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue()); - } - } - - @Test - public void usbShouldBeTornDownOnTetherError() throws Exception { - initStateMachine(TETHERING_USB); - - doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME); - dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED); - InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mCallback); - usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown(); - usbTeardownOrder.verify(mNMService).setInterfaceConfig( - IFACE_NAME, mInterfaceConfiguration); - usbTeardownOrder.verify(mCallback).updateInterfaceState( - mIpServer, STATE_AVAILABLE, TETHER_ERROR_TETHER_IFACE_ERROR); - usbTeardownOrder.verify(mCallback).updateLinkProperties( - eq(mIpServer), mLinkPropertiesCaptor.capture()); - assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue()); - } - - @Test - public void shouldTearDownUsbOnUpstreamError() throws Exception { - initTetheredStateMachine(TETHERING_USB, null); - - doThrow(RemoteException.class).when(mNMService).enableNat(anyString(), anyString()); - dispatchTetherConnectionChanged(UPSTREAM_IFACE); - InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mCallback); - usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown(); - usbTeardownOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration); - usbTeardownOrder.verify(mCallback).updateInterfaceState( - mIpServer, STATE_AVAILABLE, TETHER_ERROR_ENABLE_NAT_ERROR); - usbTeardownOrder.verify(mCallback).updateLinkProperties( - eq(mIpServer), mLinkPropertiesCaptor.capture()); - assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue()); - } - - @Test - public void ignoresDuplicateUpstreamNotifications() throws Exception { - initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE); - - verifyNoMoreInteractions(mNMService, mStatsService, mCallback); - - for (int i = 0; i < 5; i++) { - dispatchTetherConnectionChanged(UPSTREAM_IFACE); - verifyNoMoreInteractions(mNMService, mStatsService, mCallback); - } - } - - @Test - public void startsDhcpServer() throws Exception { - initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE); - dispatchTetherConnectionChanged(UPSTREAM_IFACE); - - assertDhcpStarted(new IpPrefix("192.168.43.0/24")); - } - - @Test - public void startsDhcpServerOnBluetooth() throws Exception { - initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE); - dispatchTetherConnectionChanged(UPSTREAM_IFACE); - - assertDhcpStarted(new IpPrefix("192.168.44.0/24")); - } - - @Test - public void doesNotStartDhcpServerIfDisabled() throws Exception { - initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, true /* usingLegacyDhcp */); - dispatchTetherConnectionChanged(UPSTREAM_IFACE); - - verify(mDependencies, never()).makeDhcpServer(any(), any(), any()); - } - - private void assertDhcpStarted(IpPrefix expectedPrefix) throws Exception { - verify(mDependencies, times(1)).makeDhcpServer(eq(IFACE_NAME), any(), any()); - verify(mDhcpServer, timeout(MAKE_DHCPSERVER_TIMEOUT_MS).times(1)).start(any()); - final DhcpServingParamsParcel params = mDhcpParamsCaptor.getValue(); - // Last address byte is random - assertTrue(expectedPrefix.contains(intToInet4AddressHTH(params.serverAddr))); - assertEquals(expectedPrefix.getPrefixLength(), params.serverAddrPrefixLength); - assertEquals(1, params.defaultRouters.length); - assertEquals(params.serverAddr, params.defaultRouters[0]); - assertEquals(1, params.dnsServers.length); - assertEquals(params.serverAddr, params.dnsServers[0]); - assertEquals(DHCP_LEASE_TIME_SECS, params.dhcpLeaseTimeSecs); - } - - /** - * Send a command to the state machine under test, and run the event loop to idle. - * - * @param command One of the IpServer.CMD_* constants. - * @param arg1 An additional argument to pass. - */ - private void dispatchCommand(int command, int arg1) { - mIpServer.sendMessage(command, arg1); - mLooper.dispatchAll(); - } - - /** - * Send a command to the state machine under test, and run the event loop to idle. - * - * @param command One of the IpServer.CMD_* constants. - */ - private void dispatchCommand(int command) { - mIpServer.sendMessage(command); - mLooper.dispatchAll(); - } - - /** - * Special override to tell the state machine that the upstream interface has changed. - * - * @see #dispatchCommand(int) - * @param upstreamIface String name of upstream interface (or null) - */ - private void dispatchTetherConnectionChanged(String upstreamIface) { - mIpServer.sendMessage(IpServer.CMD_TETHER_CONNECTION_CHANGED, - new InterfaceSet(upstreamIface)); - mLooper.dispatchAll(); - } - - private void assertIPv4AddressAndDirectlyConnectedRoute(LinkProperties lp) { - // Find the first IPv4 LinkAddress. - LinkAddress addr4 = null; - for (LinkAddress addr : lp.getLinkAddresses()) { - if (!(addr.getAddress() instanceof Inet4Address)) continue; - addr4 = addr; - break; - } - assertNotNull("missing IPv4 address", addr4); - - // Assert the presence of the associated directly connected route. - final RouteInfo directlyConnected = new RouteInfo(addr4, null, lp.getInterfaceName()); - assertTrue("missing directly connected route: '" + directlyConnected.toString() + "'", - lp.getRoutes().contains(directlyConnected)); - } - - private void assertNoAddressesNorRoutes(LinkProperties lp) { - assertTrue(lp.getLinkAddresses().isEmpty()); - assertTrue(lp.getRoutes().isEmpty()); - // We also check that interface name is non-empty, because we should - // never see an empty interface name in any LinkProperties update. - assertFalse(TextUtils.isEmpty(lp.getInterfaceName())); - } -} diff --git a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java index 1a3ea6096c82..02f5286506a8 100644 --- a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java +++ b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java @@ -54,7 +54,7 @@ public class ParcelableTests { builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("6.7.8.9")); builder.setAssignedV4AddressExpiry(System.currentTimeMillis() + 3_600_000); - builder.setGroupHint("groupHint"); + builder.setCluster("groupHint"); builder.setDnsAddresses(Arrays.asList( InetAddress.getByName("ACA1:652B:0911:DE8F:1200:115E:913B:AA2A"), InetAddress.getByName("6.7.8.9"))); diff --git a/tests/net/java/android/net/netlink/ConntrackMessageTest.java b/tests/net/java/android/net/netlink/ConntrackMessageTest.java deleted file mode 100644 index 5c8675770d50..000000000000 --- a/tests/net/java/android/net/netlink/ConntrackMessageTest.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.netlink; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assume.assumeTrue; - -import android.system.OsConstants; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import libcore.util.HexEncoding; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.net.Inet4Address; -import java.net.InetAddress; -import java.nio.ByteOrder; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class ConntrackMessageTest { - private static final boolean USING_LE = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN); - - // Example 1: TCP (192.168.43.209, 44333) -> (23.211.13.26, 443) - public static final String CT_V4UPDATE_TCP_HEX = - // struct nlmsghdr - "50000000" + // length = 80 - "0001" + // type = (1 << 8) | 0 - "0501" + // flags - "01000000" + // seqno = 1 - "00000000" + // pid = 0 - // struct nfgenmsg - "02" + // nfgen_family = AF_INET - "00" + // version = NFNETLINK_V0 - "0000" + // res_id - // struct nlattr - "3400" + // nla_len = 52 - "0180" + // nla_type = nested CTA_TUPLE_ORIG - // struct nlattr - "1400" + // nla_len = 20 - "0180" + // nla_type = nested CTA_TUPLE_IP - "0800 0100 C0A82BD1" + // nla_type=CTA_IP_V4_SRC, ip=192.168.43.209 - "0800 0200 17D30D1A" + // nla_type=CTA_IP_V4_DST, ip=23.211.13.26 - // struct nlattr - "1C00" + // nla_len = 28 - "0280" + // nla_type = nested CTA_TUPLE_PROTO - "0500 0100 06 000000" + // nla_type=CTA_PROTO_NUM, proto=6 - "0600 0200 AD2D 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=44333 (big endian) - "0600 0300 01BB 0000" + // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian) - // struct nlattr - "0800" + // nla_len = 8 - "0700" + // nla_type = CTA_TIMEOUT - "00069780"; // nla_value = 432000 (big endian) - public static final byte[] CT_V4UPDATE_TCP_BYTES = - HexEncoding.decode(CT_V4UPDATE_TCP_HEX.replaceAll(" ", "").toCharArray(), false); - - // Example 2: UDP (100.96.167.146, 37069) -> (216.58.197.10, 443) - public static final String CT_V4UPDATE_UDP_HEX = - // struct nlmsghdr - "50000000" + // length = 80 - "0001" + // type = (1 << 8) | 0 - "0501" + // flags - "01000000" + // seqno = 1 - "00000000" + // pid = 0 - // struct nfgenmsg - "02" + // nfgen_family = AF_INET - "00" + // version = NFNETLINK_V0 - "0000" + // res_id - // struct nlattr - "3400" + // nla_len = 52 - "0180" + // nla_type = nested CTA_TUPLE_ORIG - // struct nlattr - "1400" + // nla_len = 20 - "0180" + // nla_type = nested CTA_TUPLE_IP - "0800 0100 6460A792" + // nla_type=CTA_IP_V4_SRC, ip=100.96.167.146 - "0800 0200 D83AC50A" + // nla_type=CTA_IP_V4_DST, ip=216.58.197.10 - // struct nlattr - "1C00" + // nla_len = 28 - "0280" + // nla_type = nested CTA_TUPLE_PROTO - "0500 0100 11 000000" + // nla_type=CTA_PROTO_NUM, proto=17 - "0600 0200 90CD 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=37069 (big endian) - "0600 0300 01BB 0000" + // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian) - // struct nlattr - "0800" + // nla_len = 8 - "0700" + // nla_type = CTA_TIMEOUT - "000000B4"; // nla_value = 180 (big endian) - public static final byte[] CT_V4UPDATE_UDP_BYTES = - HexEncoding.decode(CT_V4UPDATE_UDP_HEX.replaceAll(" ", "").toCharArray(), false); - - @Test - public void testConntrackIPv4TcpTimeoutUpdate() throws Exception { - assumeTrue(USING_LE); - - final byte[] tcp = ConntrackMessage.newIPv4TimeoutUpdateRequest( - OsConstants.IPPROTO_TCP, - (Inet4Address) InetAddress.getByName("192.168.43.209"), 44333, - (Inet4Address) InetAddress.getByName("23.211.13.26"), 443, - 432000); - assertArrayEquals(CT_V4UPDATE_TCP_BYTES, tcp); - } - - @Test - public void testConntrackIPv4UdpTimeoutUpdate() throws Exception { - assumeTrue(USING_LE); - - final byte[] udp = ConntrackMessage.newIPv4TimeoutUpdateRequest( - OsConstants.IPPROTO_UDP, - (Inet4Address) InetAddress.getByName("100.96.167.146"), 37069, - (Inet4Address) InetAddress.getByName("216.58.197.10"), 443, - 180); - assertArrayEquals(CT_V4UPDATE_UDP_BYTES, udp); - } -} diff --git a/tests/net/java/android/net/netlink/InetDiagSocketTest.java b/tests/net/java/android/net/netlink/InetDiagSocketTest.java deleted file mode 100644 index 46e27c1d3d3b..000000000000 --- a/tests/net/java/android/net/netlink/InetDiagSocketTest.java +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.netlink; - -import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP; -import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST; -import static android.system.OsConstants.AF_INET; -import static android.system.OsConstants.AF_INET6; -import static android.system.OsConstants.IPPROTO_TCP; -import static android.system.OsConstants.IPPROTO_UDP; -import static android.system.OsConstants.SOCK_DGRAM; -import static android.system.OsConstants.SOCK_STREAM; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import android.app.Instrumentation; -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.netlink.StructNlMsgHdr; -import android.os.Process; -import android.system.Os; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import libcore.util.HexEncoding; - -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.FileDescriptor; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class InetDiagSocketTest { - private final String TAG = "InetDiagSocketTest"; - private ConnectivityManager mCm; - private Context mContext; - private final static int SOCKET_TIMEOUT_MS = 100; - - @Before - public void setUp() throws Exception { - Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); - mContext = instrumentation.getTargetContext(); - mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - } - - private class Connection { - public int socketDomain; - public int socketType; - public InetAddress localAddress; - public InetAddress remoteAddress; - public InetAddress localhostAddress; - public InetSocketAddress local; - public InetSocketAddress remote; - public int protocol; - public FileDescriptor localFd; - public FileDescriptor remoteFd; - - public FileDescriptor createSocket() throws Exception { - return Os.socket(socketDomain, socketType, protocol); - } - - public Connection(String to, String from) throws Exception { - remoteAddress = InetAddress.getByName(to); - if (from != null) { - localAddress = InetAddress.getByName(from); - } else { - localAddress = (remoteAddress instanceof Inet4Address) ? - Inet4Address.getByName("localhost") : Inet6Address.getByName("::"); - } - if ((localAddress instanceof Inet4Address) && (remoteAddress instanceof Inet4Address)) { - socketDomain = AF_INET; - localhostAddress = Inet4Address.getByName("localhost"); - } else { - socketDomain = AF_INET6; - localhostAddress = Inet6Address.getByName("::"); - } - } - - public void close() throws Exception { - Os.close(localFd); - } - } - - private class TcpConnection extends Connection { - public TcpConnection(String to, String from) throws Exception { - super(to, from); - protocol = IPPROTO_TCP; - socketType = SOCK_STREAM; - - remoteFd = createSocket(); - Os.bind(remoteFd, remoteAddress, 0); - Os.listen(remoteFd, 10); - int remotePort = ((InetSocketAddress) Os.getsockname(remoteFd)).getPort(); - - localFd = createSocket(); - Os.bind(localFd, localAddress, 0); - Os.connect(localFd, remoteAddress, remotePort); - - local = (InetSocketAddress) Os.getsockname(localFd); - remote = (InetSocketAddress) Os.getpeername(localFd); - } - - public void close() throws Exception { - super.close(); - Os.close(remoteFd); - } - } - private class UdpConnection extends Connection { - public UdpConnection(String to, String from) throws Exception { - super(to, from); - protocol = IPPROTO_UDP; - socketType = SOCK_DGRAM; - - remoteFd = null; - localFd = createSocket(); - Os.bind(localFd, localAddress, 0); - - Os.connect(localFd, remoteAddress, 7); - local = (InetSocketAddress) Os.getsockname(localFd); - remote = new InetSocketAddress(remoteAddress, 7); - } - } - - private void checkConnectionOwnerUid(int protocol, InetSocketAddress local, - InetSocketAddress remote, boolean expectSuccess) { - final int uid = mCm.getConnectionOwnerUid(protocol, local, remote); - - if (expectSuccess) { - assertEquals(Process.myUid(), uid); - } else { - assertNotEquals(Process.myUid(), uid); - } - } - - private int findLikelyFreeUdpPort(UdpConnection conn) throws Exception { - UdpConnection udp = new UdpConnection(conn.remoteAddress.getHostAddress(), - conn.localAddress.getHostAddress()); - final int localPort = udp.local.getPort(); - udp.close(); - return localPort; - } - - /** - * Create a test connection for UDP and TCP sockets and verify that this - * {protocol, local, remote} socket result in receiving a valid UID. - */ - public void checkGetConnectionOwnerUid(String to, String from) throws Exception { - TcpConnection tcp = new TcpConnection(to, from); - checkConnectionOwnerUid(tcp.protocol, tcp.local, tcp.remote, true); - checkConnectionOwnerUid(IPPROTO_UDP, tcp.local, tcp.remote, false); - checkConnectionOwnerUid(tcp.protocol, new InetSocketAddress(0), tcp.remote, false); - checkConnectionOwnerUid(tcp.protocol, tcp.local, new InetSocketAddress(0), false); - tcp.close(); - - UdpConnection udp = new UdpConnection(to,from); - checkConnectionOwnerUid(udp.protocol, udp.local, udp.remote, true); - checkConnectionOwnerUid(IPPROTO_TCP, udp.local, udp.remote, false); - checkConnectionOwnerUid(udp.protocol, new InetSocketAddress(findLikelyFreeUdpPort(udp)), - udp.remote, false); - udp.close(); - } - - @Test - public void testGetConnectionOwnerUid() throws Exception { - checkGetConnectionOwnerUid("::", null); - checkGetConnectionOwnerUid("::", "::"); - checkGetConnectionOwnerUid("0.0.0.0", null); - checkGetConnectionOwnerUid("0.0.0.0", "0.0.0.0"); - checkGetConnectionOwnerUid("127.0.0.1", null); - checkGetConnectionOwnerUid("127.0.0.1", "127.0.0.2"); - checkGetConnectionOwnerUid("::1", null); - checkGetConnectionOwnerUid("::1", "::1"); - } - - @Ignore("Times out on Marlin/Sailfish") - /* Verify fix for b/141603906 */ - @Test - public void testB141603906() throws Exception { - final InetSocketAddress src = new InetSocketAddress(0); - final InetSocketAddress dst = new InetSocketAddress(0); - for (int i = 1; i <= 100000; i++) { - mCm.getConnectionOwnerUid(IPPROTO_TCP, src, dst); - } - } - - // Hexadecimal representation of InetDiagReqV2 request. - private static final String INET_DIAG_REQ_V2_UDP_INET4_HEX = - // struct nlmsghdr - "48000000" + // length = 72 - "1400" + // type = SOCK_DIAG_BY_FAMILY - "0103" + // flags = NLM_F_REQUEST | NLM_F_DUMP - "00000000" + // seqno - "00000000" + // pid (0 == kernel) - // struct inet_diag_req_v2 - "02" + // family = AF_INET - "11" + // protcol = IPPROTO_UDP - "00" + // idiag_ext - "00" + // pad - "ffffffff" + // idiag_states - // inet_diag_sockid - "a5de" + // idiag_sport = 42462 - "b971" + // idiag_dport = 47473 - "0a006402000000000000000000000000" + // idiag_src = 10.0.100.2 - "08080808000000000000000000000000" + // idiag_dst = 8.8.8.8 - "00000000" + // idiag_if - "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE - private static final byte[] INET_DIAG_REQ_V2_UDP_INET4_BYTES = - HexEncoding.decode(INET_DIAG_REQ_V2_UDP_INET4_HEX.toCharArray(), false); - - @Test - public void testInetDiagReqV2UdpInet4() throws Exception { - InetSocketAddress local = new InetSocketAddress(InetAddress.getByName("10.0.100.2"), - 42462); - InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.8.8"), - 47473); - final byte[] msg = InetDiagMessage.InetDiagReqV2(IPPROTO_UDP, local, remote, AF_INET, - (short) (NLM_F_REQUEST | NLM_F_DUMP)); - assertArrayEquals(INET_DIAG_REQ_V2_UDP_INET4_BYTES, msg); - } - - // Hexadecimal representation of InetDiagReqV2 request. - private static final String INET_DIAG_REQ_V2_TCP_INET6_HEX = - // struct nlmsghdr - "48000000" + // length = 72 - "1400" + // type = SOCK_DIAG_BY_FAMILY - "0100" + // flags = NLM_F_REQUEST - "00000000" + // seqno - "00000000" + // pid (0 == kernel) - // struct inet_diag_req_v2 - "0a" + // family = AF_INET6 - "06" + // protcol = IPPROTO_TCP - "00" + // idiag_ext - "00" + // pad - "ffffffff" + // idiag_states - // inet_diag_sockid - "a5de" + // idiag_sport = 42462 - "b971" + // idiag_dport = 47473 - "fe8000000000000086c9b2fffe6aed4b" + // idiag_src = fe80::86c9:b2ff:fe6a:ed4b - "08080808000000000000000000000000" + // idiag_dst = 8.8.8.8 - "00000000" + // idiag_if - "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE - private static final byte[] INET_DIAG_REQ_V2_TCP_INET6_BYTES = - HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET6_HEX.toCharArray(), false); - - @Test - public void testInetDiagReqV2TcpInet6() throws Exception { - InetSocketAddress local = new InetSocketAddress( - InetAddress.getByName("fe80::86c9:b2ff:fe6a:ed4b"), 42462); - InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.8.8"), - 47473); - byte[] msg = InetDiagMessage.InetDiagReqV2(IPPROTO_TCP, local, remote, AF_INET6, - NLM_F_REQUEST); - - assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_BYTES, msg); - } - - // Hexadecimal representation of InetDiagReqV2 request. - private static final String INET_DIAG_MSG_HEX = - // struct nlmsghdr - "58000000" + // length = 88 - "1400" + // type = SOCK_DIAG_BY_FAMILY - "0200" + // flags = NLM_F_MULTI - "00000000" + // seqno - "f5220000" + // pid (0 == kernel) - // struct inet_diag_msg - "0a" + // family = AF_INET6 - "01" + // idiag_state - "00" + // idiag_timer - "00" + // idiag_retrans - // inet_diag_sockid - "a817" + // idiag_sport = 43031 - "960f" + // idiag_dport = 38415 - "fe8000000000000086c9b2fffe6aed4b" + // idiag_src = fe80::86c9:b2ff:fe6a:ed4b - "00000000000000000000ffff08080808" + // idiag_dst = 8.8.8.8 - "00000000" + // idiag_if - "ffffffffffffffff" + // idiag_cookie = INET_DIAG_NOCOOKIE - "00000000" + // idiag_expires - "00000000" + // idiag_rqueue - "00000000" + // idiag_wqueue - "a3270000" + // idiag_uid - "A57E1900"; // idiag_inode - private static final byte[] INET_DIAG_MSG_BYTES = - HexEncoding.decode(INET_DIAG_MSG_HEX.toCharArray(), false); - - @Test - public void testParseInetDiagResponse() throws Exception { - final ByteBuffer byteBuffer = ByteBuffer.wrap(INET_DIAG_MSG_BYTES); - byteBuffer.order(ByteOrder.LITTLE_ENDIAN); - final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer); - assertNotNull(msg); - - assertTrue(msg instanceof InetDiagMessage); - final InetDiagMessage inetDiagMsg = (InetDiagMessage) msg; - assertEquals(10147, inetDiagMsg.mStructInetDiagMsg.idiag_uid); - - final StructNlMsgHdr hdr = inetDiagMsg.getHeader(); - assertNotNull(hdr); - assertEquals(NetlinkConstants.SOCK_DIAG_BY_FAMILY, hdr.nlmsg_type); - assertEquals(StructNlMsgHdr.NLM_F_MULTI, hdr.nlmsg_flags); - assertEquals(0, hdr.nlmsg_seq); - assertEquals(8949, hdr.nlmsg_pid); - } -} diff --git a/tests/net/java/android/net/netlink/NetlinkErrorMessageTest.java b/tests/net/java/android/net/netlink/NetlinkErrorMessageTest.java deleted file mode 100644 index 44ab6051d5b3..000000000000 --- a/tests/net/java/android/net/netlink/NetlinkErrorMessageTest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2015 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.netlink; - -import static android.net.netlink.StructNlMsgHdr.NLM_F_ACK; -import static android.net.netlink.StructNlMsgHdr.NLM_F_REPLACE; -import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import android.net.netlink.NetlinkConstants; -import android.net.netlink.NetlinkErrorMessage; -import android.net.netlink.NetlinkMessage; -import android.net.netlink.StructNlMsgErr; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import libcore.util.HexEncoding; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class NetlinkErrorMessageTest { - private final String TAG = "NetlinkErrorMessageTest"; - - // Hexadecimal representation of packet capture. - public static final String NLM_ERROR_OK_HEX = - // struct nlmsghdr - "24000000" + // length = 36 - "0200" + // type = 2 (NLMSG_ERROR) - "0000" + // flags - "26350000" + // seqno - "64100000" + // pid = userspace process - // error integer - "00000000" + // "errno" (0 == OK) - // struct nlmsghdr - "30000000" + // length (48) of original request - "1C00" + // type = 28 (RTM_NEWNEIGH) - "0501" + // flags (NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE) - "26350000" + // seqno - "00000000"; // pid = kernel - public static final byte[] NLM_ERROR_OK = - HexEncoding.decode(NLM_ERROR_OK_HEX.toCharArray(), false); - - @Test - public void testParseNlmErrorOk() { - final ByteBuffer byteBuffer = ByteBuffer.wrap(NLM_ERROR_OK); - byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. - final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer); - assertNotNull(msg); - assertTrue(msg instanceof NetlinkErrorMessage); - final NetlinkErrorMessage errorMsg = (NetlinkErrorMessage) msg; - - final StructNlMsgHdr hdr = errorMsg.getHeader(); - assertNotNull(hdr); - assertEquals(36, hdr.nlmsg_len); - assertEquals(NetlinkConstants.NLMSG_ERROR, hdr.nlmsg_type); - assertEquals(0, hdr.nlmsg_flags); - assertEquals(13606, hdr.nlmsg_seq); - assertEquals(4196, hdr.nlmsg_pid); - - final StructNlMsgErr err = errorMsg.getNlMsgError(); - assertNotNull(err); - assertEquals(0, err.error); - assertNotNull(err.msg); - assertEquals(48, err.msg.nlmsg_len); - assertEquals(NetlinkConstants.RTM_NEWNEIGH, err.msg.nlmsg_type); - assertEquals((NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE), err.msg.nlmsg_flags); - assertEquals(13606, err.msg.nlmsg_seq); - assertEquals(0, err.msg.nlmsg_pid); - } -} diff --git a/tests/net/java/android/net/netlink/NetlinkSocketTest.java b/tests/net/java/android/net/netlink/NetlinkSocketTest.java deleted file mode 100644 index 3916578a3405..000000000000 --- a/tests/net/java/android/net/netlink/NetlinkSocketTest.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2015 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.netlink; - -import static android.net.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE; -import static android.system.OsConstants.NETLINK_ROUTE; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import android.net.netlink.NetlinkSocket; -import android.net.netlink.RtNetlinkNeighborMessage; -import android.net.netlink.StructNlMsgHdr; -import android.system.NetlinkSocketAddress; -import android.system.Os; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import libcore.io.IoUtils; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.FileDescriptor; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class NetlinkSocketTest { - private final String TAG = "NetlinkSocketTest"; - - @Test - public void testBasicWorkingGetNeighborsQuery() throws Exception { - final FileDescriptor fd = NetlinkSocket.forProto(NETLINK_ROUTE); - assertNotNull(fd); - - NetlinkSocket.connectToKernel(fd); - - final NetlinkSocketAddress localAddr = (NetlinkSocketAddress) Os.getsockname(fd); - assertNotNull(localAddr); - assertEquals(0, localAddr.getGroupsMask()); - assertTrue(0 != localAddr.getPortId()); - - final int TEST_SEQNO = 5; - final byte[] req = RtNetlinkNeighborMessage.newGetNeighborsRequest(TEST_SEQNO); - assertNotNull(req); - - final long TIMEOUT = 500; - assertEquals(req.length, NetlinkSocket.sendMessage(fd, req, 0, req.length, TIMEOUT)); - - int neighMessageCount = 0; - int doneMessageCount = 0; - - while (doneMessageCount == 0) { - ByteBuffer response = NetlinkSocket.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TIMEOUT); - assertNotNull(response); - assertTrue(StructNlMsgHdr.STRUCT_SIZE <= response.limit()); - assertEquals(0, response.position()); - assertEquals(ByteOrder.nativeOrder(), response.order()); - - // Verify the messages at least appears minimally reasonable. - while (response.remaining() > 0) { - final NetlinkMessage msg = NetlinkMessage.parse(response); - assertNotNull(msg); - final StructNlMsgHdr hdr = msg.getHeader(); - assertNotNull(hdr); - - if (hdr.nlmsg_type == NetlinkConstants.NLMSG_DONE) { - doneMessageCount++; - continue; - } - - assertEquals(NetlinkConstants.RTM_NEWNEIGH, hdr.nlmsg_type); - assertTrue(msg instanceof RtNetlinkNeighborMessage); - assertTrue((hdr.nlmsg_flags & StructNlMsgHdr.NLM_F_MULTI) != 0); - assertEquals(TEST_SEQNO, hdr.nlmsg_seq); - assertEquals(localAddr.getPortId(), hdr.nlmsg_pid); - - neighMessageCount++; - } - } - - assertEquals(1, doneMessageCount); - // TODO: make sure this test passes sanely in airplane mode. - assertTrue(neighMessageCount > 0); - - IoUtils.closeQuietly(fd); - } -} diff --git a/tests/net/java/android/net/netlink/RtNetlinkNeighborMessageTest.java b/tests/net/java/android/net/netlink/RtNetlinkNeighborMessageTest.java deleted file mode 100644 index 81625227f36e..000000000000 --- a/tests/net/java/android/net/netlink/RtNetlinkNeighborMessageTest.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (C) 2015 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.netlink; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import android.net.netlink.NetlinkConstants; -import android.net.netlink.NetlinkMessage; -import android.net.netlink.RtNetlinkNeighborMessage; -import android.net.netlink.StructNdMsg; -import android.net.netlink.StructNlMsgHdr; -import android.system.OsConstants; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import libcore.util.HexEncoding; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.net.Inet4Address; -import java.net.InetAddress; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class RtNetlinkNeighborMessageTest { - private final String TAG = "RtNetlinkNeighborMessageTest"; - - // Hexadecimal representation of packet capture. - public static final String RTM_DELNEIGH_HEX = - // struct nlmsghdr - "4c000000" + // length = 76 - "1d00" + // type = 29 (RTM_DELNEIGH) - "0000" + // flags - "00000000" + // seqno - "00000000" + // pid (0 == kernel) - // struct ndmsg - "02" + // family - "00" + // pad1 - "0000" + // pad2 - "15000000" + // interface index (21 == wlan0, on test device) - "0400" + // NUD state (0x04 == NUD_STALE) - "00" + // flags - "01" + // type - // struct nlattr: NDA_DST - "0800" + // length = 8 - "0100" + // type (1 == NDA_DST, for neighbor messages) - "c0a89ffe" + // IPv4 address (== 192.168.159.254) - // struct nlattr: NDA_LLADDR - "0a00" + // length = 10 - "0200" + // type (2 == NDA_LLADDR, for neighbor messages) - "00005e000164" + // MAC Address (== 00:00:5e:00:01:64) - "0000" + // padding, for 4 byte alignment - // struct nlattr: NDA_PROBES - "0800" + // length = 8 - "0400" + // type (4 == NDA_PROBES, for neighbor messages) - "01000000" + // number of probes - // struct nlattr: NDA_CACHEINFO - "1400" + // length = 20 - "0300" + // type (3 == NDA_CACHEINFO, for neighbor messages) - "05190000" + // ndm_used, as "clock ticks ago" - "05190000" + // ndm_confirmed, as "clock ticks ago" - "190d0000" + // ndm_updated, as "clock ticks ago" - "00000000"; // ndm_refcnt - public static final byte[] RTM_DELNEIGH = - HexEncoding.decode(RTM_DELNEIGH_HEX.toCharArray(), false); - - // Hexadecimal representation of packet capture. - public static final String RTM_NEWNEIGH_HEX = - // struct nlmsghdr - "58000000" + // length = 88 - "1c00" + // type = 28 (RTM_NEWNEIGH) - "0000" + // flags - "00000000" + // seqno - "00000000" + // pid (0 == kernel) - // struct ndmsg - "0a" + // family - "00" + // pad1 - "0000" + // pad2 - "15000000" + // interface index (21 == wlan0, on test device) - "0400" + // NUD state (0x04 == NUD_STALE) - "80" + // flags - "01" + // type - // struct nlattr: NDA_DST - "1400" + // length = 20 - "0100" + // type (1 == NDA_DST, for neighbor messages) - "fe8000000000000086c9b2fffe6aed4b" + // IPv6 address (== fe80::86c9:b2ff:fe6a:ed4b) - // struct nlattr: NDA_LLADDR - "0a00" + // length = 10 - "0200" + // type (2 == NDA_LLADDR, for neighbor messages) - "84c9b26aed4b" + // MAC Address (== 84:c9:b2:6a:ed:4b) - "0000" + // padding, for 4 byte alignment - // struct nlattr: NDA_PROBES - "0800" + // length = 8 - "0400" + // type (4 == NDA_PROBES, for neighbor messages) - "01000000" + // number of probes - // struct nlattr: NDA_CACHEINFO - "1400" + // length = 20 - "0300" + // type (3 == NDA_CACHEINFO, for neighbor messages) - "eb0e0000" + // ndm_used, as "clock ticks ago" - "861f0000" + // ndm_confirmed, as "clock ticks ago" - "00000000" + // ndm_updated, as "clock ticks ago" - "05000000"; // ndm_refcnt - public static final byte[] RTM_NEWNEIGH = - HexEncoding.decode(RTM_NEWNEIGH_HEX.toCharArray(), false); - - // An example of the full response from an RTM_GETNEIGH query. - private static final String RTM_GETNEIGH_RESPONSE_HEX = - // <-- struct nlmsghr -->|<-- struct ndmsg -->|<-- struct nlattr: NDA_DST -->|<-- NDA_LLADDR -->|<-- NDA_PROBES -->|<-- NDA_CACHEINFO -->| - "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff020000000000000000000000000001 0a00 0200 333300000001 0000 0800 0400 00000000 1400 0300 a2280000 32110000 32110000 01000000" + - "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ff000001 0a00 0200 3333ff000001 0000 0800 0400 00000000 1400 0300 0d280000 9d100000 9d100000 00000000" + - "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 0400 80 01 1400 0100 20010db800040ca00000000000000001 0a00 0200 84c9b26aed4b 0000 0800 0400 04000000 1400 0300 90100000 90100000 90080000 01000000" + - "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ff47da19 0a00 0200 3333ff47da19 0000 0800 0400 00000000 1400 0300 a1280000 31110000 31110000 01000000" + - "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 14000000 4000 00 05 1400 0100 ff020000000000000000000000000016 0a00 0200 333300000016 0000 0800 0400 00000000 1400 0300 912a0000 21130000 21130000 00000000" + - "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 14000000 4000 00 05 1400 0100 ff0200000000000000000001ffeace3b 0a00 0200 3333ffeace3b 0000 0800 0400 00000000 1400 0300 922a0000 22130000 22130000 00000000" + - "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ff5c2a83 0a00 0200 3333ff5c2a83 0000 0800 0400 00000000 1400 0300 391c0000 c9040000 c9040000 01000000" + - "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 01000000 4000 00 02 1400 0100 00000000000000000000000000000000 0a00 0200 000000000000 0000 0800 0400 00000000 1400 0300 cd180200 5d010200 5d010200 08000000" + - "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff020000000000000000000000000002 0a00 0200 333300000002 0000 0800 0400 00000000 1400 0300 352a0000 c5120000 c5120000 00000000" + - "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff020000000000000000000000000016 0a00 0200 333300000016 0000 0800 0400 00000000 1400 0300 982a0000 28130000 28130000 00000000" + - "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 0800 80 01 1400 0100 fe8000000000000086c9b2fffe6aed4b 0a00 0200 84c9b26aed4b 0000 0800 0400 00000000 1400 0300 23000000 24000000 57000000 13000000" + - "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ffeace3b 0a00 0200 3333ffeace3b 0000 0800 0400 00000000 1400 0300 992a0000 29130000 29130000 01000000" + - "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 14000000 4000 00 05 1400 0100 ff020000000000000000000000000002 0a00 0200 333300000002 0000 0800 0400 00000000 1400 0300 2e2a0000 be120000 be120000 00000000" + - "44000000 1c00 0200 00000000 3e2b0000 02 00 0000 18000000 4000 00 03 0800 0100 00000000 0400 0200 0800 0400 00000000 1400 0300 75280000 05110000 05110000 22000000"; - public static final byte[] RTM_GETNEIGH_RESPONSE = - HexEncoding.decode(RTM_GETNEIGH_RESPONSE_HEX.replaceAll(" ", "").toCharArray(), false); - - @Test - public void testParseRtmDelNeigh() { - final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_DELNEIGH); - byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. - final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer); - assertNotNull(msg); - assertTrue(msg instanceof RtNetlinkNeighborMessage); - final RtNetlinkNeighborMessage neighMsg = (RtNetlinkNeighborMessage) msg; - - final StructNlMsgHdr hdr = neighMsg.getHeader(); - assertNotNull(hdr); - assertEquals(76, hdr.nlmsg_len); - assertEquals(NetlinkConstants.RTM_DELNEIGH, hdr.nlmsg_type); - assertEquals(0, hdr.nlmsg_flags); - assertEquals(0, hdr.nlmsg_seq); - assertEquals(0, hdr.nlmsg_pid); - - final StructNdMsg ndmsgHdr = neighMsg.getNdHeader(); - assertNotNull(ndmsgHdr); - assertEquals((byte) OsConstants.AF_INET, ndmsgHdr.ndm_family); - assertEquals(21, ndmsgHdr.ndm_ifindex); - assertEquals(StructNdMsg.NUD_STALE, ndmsgHdr.ndm_state); - final InetAddress destination = neighMsg.getDestination(); - assertNotNull(destination); - assertEquals(InetAddress.parseNumericAddress("192.168.159.254"), destination); - } - - @Test - public void testParseRtmNewNeigh() { - final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_NEWNEIGH); - byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. - final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer); - assertNotNull(msg); - assertTrue(msg instanceof RtNetlinkNeighborMessage); - final RtNetlinkNeighborMessage neighMsg = (RtNetlinkNeighborMessage) msg; - - final StructNlMsgHdr hdr = neighMsg.getHeader(); - assertNotNull(hdr); - assertEquals(88, hdr.nlmsg_len); - assertEquals(NetlinkConstants.RTM_NEWNEIGH, hdr.nlmsg_type); - assertEquals(0, hdr.nlmsg_flags); - assertEquals(0, hdr.nlmsg_seq); - assertEquals(0, hdr.nlmsg_pid); - - final StructNdMsg ndmsgHdr = neighMsg.getNdHeader(); - assertNotNull(ndmsgHdr); - assertEquals((byte) OsConstants.AF_INET6, ndmsgHdr.ndm_family); - assertEquals(21, ndmsgHdr.ndm_ifindex); - assertEquals(StructNdMsg.NUD_STALE, ndmsgHdr.ndm_state); - final InetAddress destination = neighMsg.getDestination(); - assertNotNull(destination); - assertEquals(InetAddress.parseNumericAddress("fe80::86c9:b2ff:fe6a:ed4b"), destination); - } - - @Test - public void testParseRtmGetNeighResponse() { - final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_GETNEIGH_RESPONSE); - byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. - - int messageCount = 0; - while (byteBuffer.remaining() > 0) { - final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer); - assertNotNull(msg); - assertTrue(msg instanceof RtNetlinkNeighborMessage); - final RtNetlinkNeighborMessage neighMsg = (RtNetlinkNeighborMessage) msg; - - final StructNlMsgHdr hdr = neighMsg.getHeader(); - assertNotNull(hdr); - assertEquals(NetlinkConstants.RTM_NEWNEIGH, hdr.nlmsg_type); - assertEquals(StructNlMsgHdr.NLM_F_MULTI, hdr.nlmsg_flags); - assertEquals(0, hdr.nlmsg_seq); - assertEquals(11070, hdr.nlmsg_pid); - - messageCount++; - } - // TODO: add more detailed spot checks. - assertEquals(14, messageCount); - } - - @Test - public void testCreateRtmNewNeighMessage() { - final int seqNo = 2635; - final int ifIndex = 14; - final byte[] llAddr = - new byte[] { (byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5, (byte) 6 }; - - // Hexadecimal representation of our created packet. - final String expectedNewNeighHex = - // struct nlmsghdr - "30000000" + // length = 48 - "1c00" + // type = 28 (RTM_NEWNEIGH) - "0501" + // flags (NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE) - "4b0a0000" + // seqno - "00000000" + // pid (0 == kernel) - // struct ndmsg - "02" + // family - "00" + // pad1 - "0000" + // pad2 - "0e000000" + // interface index (14) - "0800" + // NUD state (0x08 == NUD_DELAY) - "00" + // flags - "00" + // type - // struct nlattr: NDA_DST - "0800" + // length = 8 - "0100" + // type (1 == NDA_DST, for neighbor messages) - "7f000001" + // IPv4 address (== 127.0.0.1) - // struct nlattr: NDA_LLADDR - "0a00" + // length = 10 - "0200" + // type (2 == NDA_LLADDR, for neighbor messages) - "010203040506" + // MAC Address (== 01:02:03:04:05:06) - "0000"; // padding, for 4 byte alignment - final byte[] expectedNewNeigh = - HexEncoding.decode(expectedNewNeighHex.toCharArray(), false); - - final byte[] bytes = RtNetlinkNeighborMessage.newNewNeighborMessage( - seqNo, Inet4Address.LOOPBACK, StructNdMsg.NUD_DELAY, ifIndex, llAddr); - if (!Arrays.equals(expectedNewNeigh, bytes)) { - assertEquals(expectedNewNeigh.length, bytes.length); - for (int i = 0; i < Math.min(expectedNewNeigh.length, bytes.length); i++) { - assertEquals(expectedNewNeigh[i], bytes[i]); - } - } - } -} diff --git a/tests/net/java/android/net/nsd/NsdManagerTest.java b/tests/net/java/android/net/nsd/NsdManagerTest.java index 2d2bccba7eb9..cf7587a2039f 100644 --- a/tests/net/java/android/net/nsd/NsdManagerTest.java +++ b/tests/net/java/android/net/nsd/NsdManagerTest.java @@ -16,8 +16,6 @@ package android.net.nsd; -import static com.android.internal.util.TestUtils.waitForIdleHandler; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; @@ -40,6 +38,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.AsyncChannel; +import com.android.testutils.HandlerUtilsKt; import org.junit.After; import org.junit.Before; @@ -74,7 +73,7 @@ public class NsdManagerTest { @After public void tearDown() throws Exception { - mServiceHandler.waitForIdle(mTimeoutMs); + HandlerUtilsKt.waitForIdle(mServiceHandler, mTimeoutMs); mServiceHandler.chan.disconnect(); mServiceHandler.stop(); if (mManager != null) { @@ -334,7 +333,7 @@ public class NsdManagerTest { } int verifyRequest(int expectedMessageType) { - mServiceHandler.waitForIdle(mTimeoutMs); + HandlerUtilsKt.waitForIdle(mServiceHandler, mTimeoutMs); verify(mServiceHandler, timeout(mTimeoutMs)).handleMessage(any()); reset(mServiceHandler); Message received = mServiceHandler.getLastMessage(); @@ -366,10 +365,6 @@ public class NsdManagerTest { lastMessage.copyFrom(msg); } - void waitForIdle(long timeoutMs) { - waitForIdleHandler(this, timeoutMs); - } - @Override public void handleMessage(Message msg) { setLastMessage(msg); diff --git a/tests/net/java/android/net/shared/Inet4AddressUtilsTest.java b/tests/net/java/android/net/shared/Inet4AddressUtilsTest.java deleted file mode 100644 index 35f8c790ad42..000000000000 --- a/tests/net/java/android/net/shared/Inet4AddressUtilsTest.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.shared; - -import static android.net.shared.Inet4AddressUtils.getBroadcastAddress; -import static android.net.shared.Inet4AddressUtils.getImplicitNetmask; -import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address; -import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH; -import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTL; -import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH; -import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTL; -import static android.net.shared.Inet4AddressUtils.netmaskToPrefixLength; -import static android.net.shared.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH; -import static android.net.shared.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTL; - -import static junit.framework.Assert.assertEquals; - -import static org.junit.Assert.fail; - -import android.net.InetAddresses; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.net.Inet4Address; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class Inet4AddressUtilsTest { - - @Test - public void testInet4AddressToIntHTL() { - assertEquals(0, inet4AddressToIntHTL(ipv4Address("0.0.0.0"))); - assertEquals(0x000080ff, inet4AddressToIntHTL(ipv4Address("255.128.0.0"))); - assertEquals(0x0080ff0a, inet4AddressToIntHTL(ipv4Address("10.255.128.0"))); - assertEquals(0x00feff0a, inet4AddressToIntHTL(ipv4Address("10.255.254.0"))); - assertEquals(0xfeffa8c0, inet4AddressToIntHTL(ipv4Address("192.168.255.254"))); - assertEquals(0xffffa8c0, inet4AddressToIntHTL(ipv4Address("192.168.255.255"))); - } - - @Test - public void testIntToInet4AddressHTL() { - assertEquals(ipv4Address("0.0.0.0"), intToInet4AddressHTL(0)); - assertEquals(ipv4Address("255.128.0.0"), intToInet4AddressHTL(0x000080ff)); - assertEquals(ipv4Address("10.255.128.0"), intToInet4AddressHTL(0x0080ff0a)); - assertEquals(ipv4Address("10.255.254.0"), intToInet4AddressHTL(0x00feff0a)); - assertEquals(ipv4Address("192.168.255.254"), intToInet4AddressHTL(0xfeffa8c0)); - assertEquals(ipv4Address("192.168.255.255"), intToInet4AddressHTL(0xffffa8c0)); - } - - @Test - public void testInet4AddressToIntHTH() { - assertEquals(0, inet4AddressToIntHTH(ipv4Address("0.0.0.0"))); - assertEquals(0xff800000, inet4AddressToIntHTH(ipv4Address("255.128.0.0"))); - assertEquals(0x0aff8000, inet4AddressToIntHTH(ipv4Address("10.255.128.0"))); - assertEquals(0x0afffe00, inet4AddressToIntHTH(ipv4Address("10.255.254.0"))); - assertEquals(0xc0a8fffe, inet4AddressToIntHTH(ipv4Address("192.168.255.254"))); - assertEquals(0xc0a8ffff, inet4AddressToIntHTH(ipv4Address("192.168.255.255"))); - } - - @Test - public void testIntToInet4AddressHTH() { - assertEquals(ipv4Address("0.0.0.0"), intToInet4AddressHTH(0)); - assertEquals(ipv4Address("255.128.0.0"), intToInet4AddressHTH(0xff800000)); - assertEquals(ipv4Address("10.255.128.0"), intToInet4AddressHTH(0x0aff8000)); - assertEquals(ipv4Address("10.255.254.0"), intToInet4AddressHTH(0x0afffe00)); - assertEquals(ipv4Address("192.168.255.254"), intToInet4AddressHTH(0xc0a8fffe)); - assertEquals(ipv4Address("192.168.255.255"), intToInet4AddressHTH(0xc0a8ffff)); - } - - - @Test - public void testPrefixLengthToV4NetmaskIntHTL() { - assertEquals(0, prefixLengthToV4NetmaskIntHTL(0)); - assertEquals(0x000080ff /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTL(9)); - assertEquals(0x0080ffff /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTL(17)); - assertEquals(0x00feffff /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTL(23)); - assertEquals(0xfeffffff /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTL(31)); - assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTL(32)); - } - - @Test - public void testPrefixLengthToV4NetmaskIntHTH() { - assertEquals(0, prefixLengthToV4NetmaskIntHTH(0)); - assertEquals(0xff800000 /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTH(9)); - assertEquals(0xffff8000 /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTH(17)); - assertEquals(0xfffffe00 /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTH(23)); - assertEquals(0xfffffffe /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTH(31)); - assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTH(32)); - } - - @Test(expected = IllegalArgumentException.class) - public void testPrefixLengthToV4NetmaskIntHTH_NegativeLength() { - prefixLengthToV4NetmaskIntHTH(-1); - } - - @Test(expected = IllegalArgumentException.class) - public void testPrefixLengthToV4NetmaskIntHTH_LengthTooLarge() { - prefixLengthToV4NetmaskIntHTH(33); - } - - private void checkAddressMasking(String expectedAddr, String addr, int prefixLength) { - final int prefix = prefixLengthToV4NetmaskIntHTH(prefixLength); - final int addrInt = inet4AddressToIntHTH(ipv4Address(addr)); - assertEquals(ipv4Address(expectedAddr), intToInet4AddressHTH(prefix & addrInt)); - } - - @Test - public void testPrefixLengthToV4NetmaskIntHTH_MaskAddr() { - checkAddressMasking("192.168.0.0", "192.168.128.1", 16); - checkAddressMasking("255.240.0.0", "255.255.255.255", 12); - checkAddressMasking("255.255.255.255", "255.255.255.255", 32); - checkAddressMasking("0.0.0.0", "255.255.255.255", 0); - } - - @Test - public void testGetImplicitNetmask() { - assertEquals(8, getImplicitNetmask(ipv4Address("4.2.2.2"))); - assertEquals(8, getImplicitNetmask(ipv4Address("10.5.6.7"))); - assertEquals(16, getImplicitNetmask(ipv4Address("173.194.72.105"))); - assertEquals(16, getImplicitNetmask(ipv4Address("172.23.68.145"))); - assertEquals(24, getImplicitNetmask(ipv4Address("192.0.2.1"))); - assertEquals(24, getImplicitNetmask(ipv4Address("192.168.5.1"))); - assertEquals(32, getImplicitNetmask(ipv4Address("224.0.0.1"))); - assertEquals(32, getImplicitNetmask(ipv4Address("255.6.7.8"))); - } - - private void assertInvalidNetworkMask(Inet4Address addr) { - try { - netmaskToPrefixLength(addr); - fail("Invalid netmask " + addr.getHostAddress() + " did not cause exception"); - } catch (IllegalArgumentException expected) { - } - } - - @Test - public void testNetmaskToPrefixLength() { - assertEquals(0, netmaskToPrefixLength(ipv4Address("0.0.0.0"))); - assertEquals(9, netmaskToPrefixLength(ipv4Address("255.128.0.0"))); - assertEquals(17, netmaskToPrefixLength(ipv4Address("255.255.128.0"))); - assertEquals(23, netmaskToPrefixLength(ipv4Address("255.255.254.0"))); - assertEquals(31, netmaskToPrefixLength(ipv4Address("255.255.255.254"))); - assertEquals(32, netmaskToPrefixLength(ipv4Address("255.255.255.255"))); - - assertInvalidNetworkMask(ipv4Address("0.0.0.1")); - assertInvalidNetworkMask(ipv4Address("255.255.255.253")); - assertInvalidNetworkMask(ipv4Address("255.255.0.255")); - } - - @Test - public void testGetPrefixMaskAsAddress() { - assertEquals("255.255.240.0", getPrefixMaskAsInet4Address(20).getHostAddress()); - assertEquals("255.0.0.0", getPrefixMaskAsInet4Address(8).getHostAddress()); - assertEquals("0.0.0.0", getPrefixMaskAsInet4Address(0).getHostAddress()); - assertEquals("255.255.255.255", getPrefixMaskAsInet4Address(32).getHostAddress()); - } - - @Test - public void testGetBroadcastAddress() { - assertEquals("192.168.15.255", - getBroadcastAddress(ipv4Address("192.168.0.123"), 20).getHostAddress()); - assertEquals("192.255.255.255", - getBroadcastAddress(ipv4Address("192.168.0.123"), 8).getHostAddress()); - assertEquals("192.168.0.123", - getBroadcastAddress(ipv4Address("192.168.0.123"), 32).getHostAddress()); - assertEquals("255.255.255.255", - getBroadcastAddress(ipv4Address("192.168.0.123"), 0).getHostAddress()); - } - - @Test(expected = IllegalArgumentException.class) - public void testGetBroadcastAddress_PrefixTooLarge() { - getBroadcastAddress(ipv4Address("192.168.0.123"), 33); - } - - @Test(expected = IllegalArgumentException.class) - public void testGetBroadcastAddress_NegativePrefix() { - getBroadcastAddress(ipv4Address("192.168.0.123"), -1); - } - - @Test(expected = IllegalArgumentException.class) - public void testGetPrefixMaskAsAddress_PrefixTooLarge() { - getPrefixMaskAsInet4Address(33); - } - - @Test(expected = IllegalArgumentException.class) - public void testGetPrefixMaskAsAddress_NegativePrefix() { - getPrefixMaskAsInet4Address(-1); - } - - private Inet4Address ipv4Address(String addr) { - return (Inet4Address) InetAddresses.parseNumericAddress(addr); - } -} diff --git a/tests/net/java/android/net/shared/InitialConfigurationTest.java b/tests/net/java/android/net/shared/InitialConfigurationTest.java deleted file mode 100644 index 2fb8b19abcd4..000000000000 --- a/tests/net/java/android/net/shared/InitialConfigurationTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.shared; - -import static android.net.InetAddresses.parseNumericAddress; - -import static com.android.internal.util.ParcelableTestUtil.assertFieldCountEquals; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; - -import android.net.IpPrefix; -import android.net.LinkAddress; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.Arrays; -import java.util.function.Consumer; - -/** - * Tests for {@link InitialConfiguration} - */ -@RunWith(AndroidJUnit4.class) -@SmallTest -public class InitialConfigurationTest { - private InitialConfiguration mConfig; - - @Before - public void setUp() { - mConfig = new InitialConfiguration(); - mConfig.ipAddresses.addAll(Arrays.asList( - new LinkAddress(parseNumericAddress("192.168.45.45"), 16), - new LinkAddress(parseNumericAddress("2001:db8::45"), 33))); - mConfig.directlyConnectedRoutes.addAll(Arrays.asList( - new IpPrefix(parseNumericAddress("192.168.46.46"), 17), - new IpPrefix(parseNumericAddress("2001:db8::46"), 34))); - mConfig.dnsServers.addAll(Arrays.asList( - parseNumericAddress("192.168.47.47"), - parseNumericAddress("2001:db8::47"))); - // Any added InitialConfiguration field must be included in equals() to be tested properly - assertFieldCountEquals(3, InitialConfiguration.class); - } - - @Test - public void testParcelUnparcelInitialConfiguration() { - final InitialConfiguration unparceled = - InitialConfiguration.fromStableParcelable(mConfig.toStableParcelable()); - assertEquals(mConfig, unparceled); - } - - @Test - public void testEquals() { - assertEquals(mConfig, InitialConfiguration.copy(mConfig)); - - assertNotEqualsAfterChange(c -> c.ipAddresses.add( - new LinkAddress(parseNumericAddress("192.168.47.47"), 24))); - assertNotEqualsAfterChange(c -> c.directlyConnectedRoutes.add( - new IpPrefix(parseNumericAddress("192.168.46.46"), 32))); - assertNotEqualsAfterChange(c -> c.dnsServers.add(parseNumericAddress("2001:db8::49"))); - assertFieldCountEquals(3, InitialConfiguration.class); - } - - private void assertNotEqualsAfterChange(Consumer<InitialConfiguration> mutator) { - final InitialConfiguration newConfig = InitialConfiguration.copy(mConfig); - mutator.accept(newConfig); - assertNotEquals(mConfig, newConfig); - } -} diff --git a/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java b/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java deleted file mode 100644 index f9dbdc7fbf3e..000000000000 --- a/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.shared; - -import static android.net.InetAddresses.parseNumericAddress; -import static android.net.shared.IpConfigurationParcelableUtil.fromStableParcelable; -import static android.net.shared.IpConfigurationParcelableUtil.toStableParcelable; - -import static com.android.internal.util.ParcelableTestUtil.assertFieldCountEquals; - -import static org.junit.Assert.assertEquals; - -import android.net.DhcpResults; -import android.net.LinkAddress; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.net.Inet4Address; - -/** - * Tests for {@link IpConfigurationParcelableUtil}. - */ -@RunWith(AndroidJUnit4.class) -@SmallTest -public class IpConfigurationParcelableUtilTest { - private DhcpResults mDhcpResults; - - @Before - public void setUp() { - mDhcpResults = new DhcpResults(); - mDhcpResults.ipAddress = new LinkAddress(parseNumericAddress("2001:db8::42"), 64); - mDhcpResults.gateway = parseNumericAddress("192.168.42.42"); - mDhcpResults.dnsServers.add(parseNumericAddress("2001:db8::43")); - mDhcpResults.dnsServers.add(parseNumericAddress("192.168.43.43")); - mDhcpResults.domains = "example.com"; - mDhcpResults.serverAddress = (Inet4Address) parseNumericAddress("192.168.44.44"); - mDhcpResults.vendorInfo = "TEST_VENDOR_INFO"; - mDhcpResults.leaseDuration = 3600; - mDhcpResults.serverHostName = "dhcp.example.com"; - mDhcpResults.mtu = 1450; - // Any added DhcpResults field must be included in equals() to be tested properly - assertFieldCountEquals(9, DhcpResults.class); - } - - @Test - public void testParcelUnparcelDhcpResults() { - doDhcpResultsParcelUnparcelTest(); - } - - @Test - public void testParcelUnparcelDhcpResults_NullIpAddress() { - mDhcpResults.ipAddress = null; - doDhcpResultsParcelUnparcelTest(); - } - - @Test - public void testParcelUnparcelDhcpResults_NullGateway() { - mDhcpResults.gateway = null; - doDhcpResultsParcelUnparcelTest(); - } - - @Test - public void testParcelUnparcelDhcpResults_NullDomains() { - mDhcpResults.domains = null; - doDhcpResultsParcelUnparcelTest(); - } - - @Test - public void testParcelUnparcelDhcpResults_EmptyDomains() { - mDhcpResults.domains = ""; - doDhcpResultsParcelUnparcelTest(); - } - - @Test - public void testParcelUnparcelDhcpResults_NullServerAddress() { - mDhcpResults.serverAddress = null; - doDhcpResultsParcelUnparcelTest(); - } - - @Test - public void testParcelUnparcelDhcpResults_NullVendorInfo() { - mDhcpResults.vendorInfo = null; - doDhcpResultsParcelUnparcelTest(); - } - - @Test - public void testParcelUnparcelDhcpResults_NullServerHostName() { - mDhcpResults.serverHostName = null; - doDhcpResultsParcelUnparcelTest(); - } - - private void doDhcpResultsParcelUnparcelTest() { - final DhcpResults unparceled = fromStableParcelable(toStableParcelable(mDhcpResults)); - assertEquals(mDhcpResults, unparceled); - } -} diff --git a/tests/net/java/android/net/shared/ProvisioningConfigurationTest.java b/tests/net/java/android/net/shared/ProvisioningConfigurationTest.java deleted file mode 100644 index 382afe0279be..000000000000 --- a/tests/net/java/android/net/shared/ProvisioningConfigurationTest.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.shared; - -import static android.net.InetAddresses.parseNumericAddress; -import static android.net.shared.ProvisioningConfiguration.fromStableParcelable; - -import static com.android.internal.util.ParcelableTestUtil.assertFieldCountEquals; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; - -import android.net.LinkAddress; -import android.net.Network; -import android.net.StaticIpConfiguration; -import android.net.apf.ApfCapabilities; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.function.Consumer; - -/** - * Tests for {@link ProvisioningConfiguration}. - */ -@RunWith(AndroidJUnit4.class) -@SmallTest -public class ProvisioningConfigurationTest { - private ProvisioningConfiguration mConfig; - - @Before - public void setUp() { - mConfig = new ProvisioningConfiguration(); - mConfig.mEnableIPv4 = true; - mConfig.mEnableIPv6 = true; - mConfig.mUsingMultinetworkPolicyTracker = true; - mConfig.mUsingIpReachabilityMonitor = true; - mConfig.mRequestedPreDhcpActionMs = 42; - mConfig.mInitialConfig = new InitialConfiguration(); - mConfig.mInitialConfig.ipAddresses.add( - new LinkAddress(parseNumericAddress("192.168.42.42"), 24)); - mConfig.mStaticIpConfig = new StaticIpConfiguration(); - mConfig.mStaticIpConfig.ipAddress = - new LinkAddress(parseNumericAddress("2001:db8::42"), 90); - // Not testing other InitialConfig or StaticIpConfig members: they have their own unit tests - mConfig.mApfCapabilities = new ApfCapabilities(1, 2, 3); - mConfig.mProvisioningTimeoutMs = 4200; - mConfig.mIPv6AddrGenMode = 123; - mConfig.mNetwork = new Network(321); - mConfig.mDisplayName = "test_config"; - // Any added field must be included in equals() to be tested properly - assertFieldCountEquals(12, ProvisioningConfiguration.class); - } - - @Test - public void testParcelUnparcel() { - doParcelUnparcelTest(); - } - - @Test - public void testParcelUnparcel_NullInitialConfiguration() { - mConfig.mInitialConfig = null; - doParcelUnparcelTest(); - } - - @Test - public void testParcelUnparcel_NullStaticConfiguration() { - mConfig.mStaticIpConfig = null; - doParcelUnparcelTest(); - } - - @Test - public void testParcelUnparcel_NullApfCapabilities() { - mConfig.mApfCapabilities = null; - doParcelUnparcelTest(); - } - - @Test - public void testParcelUnparcel_NullNetwork() { - mConfig.mNetwork = null; - doParcelUnparcelTest(); - } - - private void doParcelUnparcelTest() { - final ProvisioningConfiguration unparceled = - fromStableParcelable(mConfig.toStableParcelable()); - assertEquals(mConfig, unparceled); - } - - @Test - public void testEquals() { - assertEquals(mConfig, new ProvisioningConfiguration(mConfig)); - - assertNotEqualsAfterChange(c -> c.mEnableIPv4 = false); - assertNotEqualsAfterChange(c -> c.mEnableIPv6 = false); - assertNotEqualsAfterChange(c -> c.mUsingMultinetworkPolicyTracker = false); - assertNotEqualsAfterChange(c -> c.mUsingIpReachabilityMonitor = false); - assertNotEqualsAfterChange(c -> c.mRequestedPreDhcpActionMs++); - assertNotEqualsAfterChange(c -> c.mInitialConfig.ipAddresses.add( - new LinkAddress(parseNumericAddress("192.168.47.47"), 16))); - assertNotEqualsAfterChange(c -> c.mInitialConfig = null); - assertNotEqualsAfterChange(c -> c.mStaticIpConfig.ipAddress = - new LinkAddress(parseNumericAddress("2001:db8::47"), 64)); - assertNotEqualsAfterChange(c -> c.mStaticIpConfig = null); - assertNotEqualsAfterChange(c -> c.mApfCapabilities = new ApfCapabilities(4, 5, 6)); - assertNotEqualsAfterChange(c -> c.mApfCapabilities = null); - assertNotEqualsAfterChange(c -> c.mProvisioningTimeoutMs++); - assertNotEqualsAfterChange(c -> c.mIPv6AddrGenMode++); - assertNotEqualsAfterChange(c -> c.mNetwork = new Network(123)); - assertNotEqualsAfterChange(c -> c.mNetwork = null); - assertNotEqualsAfterChange(c -> c.mDisplayName = "other_test"); - assertNotEqualsAfterChange(c -> c.mDisplayName = null); - assertFieldCountEquals(12, ProvisioningConfiguration.class); - } - - private void assertNotEqualsAfterChange(Consumer<ProvisioningConfiguration> mutator) { - final ProvisioningConfiguration newConfig = new ProvisioningConfiguration(mConfig); - mutator.accept(newConfig); - assertNotEquals(mConfig, newConfig); - } -} diff --git a/tests/net/java/android/net/util/DnsUtilsTest.java b/tests/net/java/android/net/util/DnsUtilsTest.java index 42e340bbcba9..b626db8d89e4 100644 --- a/tests/net/java/android/net/util/DnsUtilsTest.java +++ b/tests/net/java/android/net/util/DnsUtilsTest.java @@ -57,24 +57,38 @@ public class DnsUtilsTest { @Test public void testRfc6724Comparator() { final List<DnsUtils.SortableAddress> test = Arrays.asList( - makeSortableAddress("216.58.200.36"), // Ipv4 - makeSortableAddress("2404:6800:4008:801::2004"), // global - makeSortableAddress("::1"), // loop back - makeSortableAddress("fe80::c46f:1cff:fe04:39b4"), // link local - makeSortableAddress("::ffff:192.168.95.3"), // IPv4-mapped IPv6 - makeSortableAddress("2001::47c1"), // teredo tunneling - makeSortableAddress("::216.58.200.36"), // IPv4-compatible - makeSortableAddress("3ffe::1234:5678")); // 6bone + // Ipv4 + makeSortableAddress("216.58.200.36", "192.168.1.1"), + // global with different scope src + makeSortableAddress("2404:6800:4008:801::2004", "fe80::1111:2222"), + // global without src addr + makeSortableAddress("2404:6800:cafe:801::1"), + // loop back + makeSortableAddress("::1", "::1"), + // link local + makeSortableAddress("fe80::c46f:1cff:fe04:39b4", "fe80::1"), + // teredo tunneling + makeSortableAddress("2001::47c1", "2001::2"), + // 6bone without src addr + makeSortableAddress("3ffe::1234:5678"), + // IPv4-compatible + makeSortableAddress("::216.58.200.36", "::216.58.200.9"), + // 6bone + makeSortableAddress("3ffe::1234:5678", "3ffe::1234:1"), + // IPv4-mapped IPv6 + makeSortableAddress("::ffff:192.168.95.7", "::ffff:192.168.95.1")); final List<InetAddress> expected = Arrays.asList( stringToAddress("::1"), // loop back stringToAddress("fe80::c46f:1cff:fe04:39b4"), // link local - stringToAddress("2404:6800:4008:801::2004"), // global stringToAddress("216.58.200.36"), // Ipv4 - stringToAddress("::ffff:192.168.95.3"), // IPv4-mapped IPv6 + stringToAddress("::ffff:192.168.95.7"), // IPv4-mapped IPv6 stringToAddress("2001::47c1"), // teredo tunneling - stringToAddress("::216.58.200.36"), // IPv4-compatible - stringToAddress("3ffe::1234:5678")); // 6bone + stringToAddress("::216.58.200.36"), // IPv4-compatible + stringToAddress("3ffe::1234:5678"), // 6bone + stringToAddress("2404:6800:4008:801::2004"), // global with different scope src + stringToAddress("2404:6800:cafe:801::1"), // global without src addr + stringToAddress("3ffe::1234:5678")); // 6bone without src addr Collections.sort(test, new DnsUtils.Rfc6724Comparator()); diff --git a/tests/net/java/android/net/util/InterfaceParamsTest.java b/tests/net/java/android/net/util/InterfaceParamsTest.java deleted file mode 100644 index 141455cb179a..000000000000 --- a/tests/net/java/android/net/util/InterfaceParamsTest.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.util; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class InterfaceParamsTest { - @Test - public void testNullInterfaceReturnsNull() { - assertNull(InterfaceParams.getByName(null)); - } - - @Test - public void testNonExistentInterfaceReturnsNull() { - assertNull(InterfaceParams.getByName("doesnotexist0")); - } - - @Test - public void testLoopback() { - final InterfaceParams ifParams = InterfaceParams.getByName("lo"); - assertNotNull(ifParams); - assertEquals("lo", ifParams.name); - assertTrue(ifParams.index > 0); - assertNotNull(ifParams.macAddr); - assertTrue(ifParams.defaultMtu >= NetworkConstants.ETHER_MTU); - } -} diff --git a/tests/net/java/android/net/util/InterfaceSetTest.java b/tests/net/java/android/net/util/InterfaceSetTest.java deleted file mode 100644 index ea084b607868..000000000000 --- a/tests/net/java/android/net/util/InterfaceSetTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.util; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class InterfaceSetTest { - @Test - public void testNullNamesIgnored() { - final InterfaceSet set = new InterfaceSet(null, "if1", null, "if2", null); - assertEquals(2, set.ifnames.size()); - assertTrue(set.ifnames.contains("if1")); - assertTrue(set.ifnames.contains("if2")); - } - - @Test - public void testToString() { - final InterfaceSet set = new InterfaceSet("if1", "if2"); - final String setString = set.toString(); - assertTrue(setString.equals("[if1,if2]") || setString.equals("[if2,if1]")); - } - - @Test - public void testToString_Empty() { - final InterfaceSet set = new InterfaceSet(null, null); - assertEquals("[]", set.toString()); - } - - @Test - public void testEquals() { - assertEquals(new InterfaceSet(null, "if1", "if2"), new InterfaceSet("if2", "if1")); - assertEquals(new InterfaceSet(null, null), new InterfaceSet()); - assertFalse(new InterfaceSet("if1", "if3").equals(new InterfaceSet("if1", "if2"))); - assertFalse(new InterfaceSet("if1", "if2").equals(new InterfaceSet("if1"))); - assertFalse(new InterfaceSet().equals(null)); - } -} diff --git a/tests/net/java/android/net/util/SharedLogTest.java b/tests/net/java/android/net/util/SharedLogTest.java deleted file mode 100644 index e1dba3677121..000000000000 --- a/tests/net/java/android/net/util/SharedLogTest.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.util; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.ByteArrayOutputStream; -import java.io.PrintWriter; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class SharedLogTest { - private static final String TIMESTAMP_PATTERN = "\\d{2}:\\d{2}:\\d{2}"; - private static final String TIMESTAMP = "HH:MM:SS"; - - @Test - public void testBasicOperation() { - final SharedLog logTop = new SharedLog("top"); - logTop.mark("first post!"); - - final SharedLog logLevel2a = logTop.forSubComponent("twoA"); - final SharedLog logLevel2b = logTop.forSubComponent("twoB"); - logLevel2b.e("2b or not 2b"); - logLevel2b.e("No exception", null); - logLevel2b.e("Wait, here's one", new Exception("Test")); - logLevel2a.w("second post?"); - - final SharedLog logLevel3 = logLevel2a.forSubComponent("three"); - logTop.log("still logging"); - logLevel3.log("3 >> 2"); - logLevel2a.mark("ok: last post"); - - final String[] expected = { - " - MARK first post!", - " - [twoB] ERROR 2b or not 2b", - " - [twoB] ERROR No exception", - // No stacktrace in shared log, only in logcat - " - [twoB] ERROR Wait, here's one: Test", - " - [twoA] WARN second post?", - " - still logging", - " - [twoA.three] 3 >> 2", - " - [twoA] MARK ok: last post", - }; - // Verify the logs are all there and in the correct order. - verifyLogLines(expected, logTop); - - // In fact, because they all share the same underlying LocalLog, - // every subcomponent SharedLog's dump() is identical. - verifyLogLines(expected, logLevel2a); - verifyLogLines(expected, logLevel2b); - verifyLogLines(expected, logLevel3); - } - - private static void verifyLogLines(String[] expected, SharedLog log) { - final ByteArrayOutputStream ostream = new ByteArrayOutputStream(); - final PrintWriter pw = new PrintWriter(ostream, true); - log.dump(null, pw, null); - - final String dumpOutput = ostream.toString(); - assertTrue(dumpOutput != null); - assertTrue(!"".equals(dumpOutput)); - - final String[] lines = dumpOutput.split("\n"); - assertEquals(expected.length, lines.length); - - for (int i = 0; i < expected.length; i++) { - String got = lines[i]; - String want = expected[i]; - assertTrue(String.format("'%s' did not contain '%s'", got, want), got.endsWith(want)); - assertTrue(String.format("'%s' did not contain a %s timestamp", got, TIMESTAMP), - got.replaceFirst(TIMESTAMP_PATTERN, TIMESTAMP).contains(TIMESTAMP)); - } - } -} diff --git a/tests/net/java/android/net/util/VersionedBroadcastListenerTest.java b/tests/net/java/android/net/util/VersionedBroadcastListenerTest.java deleted file mode 100644 index 0d27d5bf0a11..000000000000 --- a/tests/net/java/android/net/util/VersionedBroadcastListenerTest.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.util; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.reset; - -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Handler; -import android.os.Looper; -import android.os.UserHandle; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.internal.util.test.BroadcastInterceptingContext; - -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class VersionedBroadcastListenerTest { - private static final String TAG = VersionedBroadcastListenerTest.class.getSimpleName(); - private static final String ACTION_TEST = "action.test.happy.broadcasts"; - - @Mock private Context mContext; - private BroadcastInterceptingContext mServiceContext; - private Handler mHandler; - private VersionedBroadcastListener mListener; - private int mCallbackCount; - - private void doCallback() { mCallbackCount++; } - - private class MockContext extends BroadcastInterceptingContext { - MockContext(Context base) { - super(base); - } - } - - @BeforeClass - public static void setUpBeforeClass() throws Exception { - if (Looper.myLooper() == null) { - Looper.prepare(); - } - } - - @Before public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - reset(mContext); - mServiceContext = new MockContext(mContext); - mHandler = new Handler(Looper.myLooper()); - mCallbackCount = 0; - final IntentFilter filter = new IntentFilter(); - filter.addAction(ACTION_TEST); - mListener = new VersionedBroadcastListener( - TAG, mServiceContext, mHandler, filter, (Intent intent) -> doCallback()); - } - - @After public void tearDown() throws Exception { - if (mListener != null) { - mListener.stopListening(); - mListener = null; - } - } - - private void sendBroadcast() { - final Intent intent = new Intent(ACTION_TEST); - mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); - } - - @Test - public void testBasicListening() { - assertEquals(0, mCallbackCount); - mListener.startListening(); - for (int i = 0; i < 5; i++) { - sendBroadcast(); - assertEquals(i+1, mCallbackCount); - } - mListener.stopListening(); - } - - @Test - public void testBroadcastsBeforeStartAreIgnored() { - assertEquals(0, mCallbackCount); - for (int i = 0; i < 5; i++) { - sendBroadcast(); - assertEquals(0, mCallbackCount); - } - - mListener.startListening(); - sendBroadcast(); - assertEquals(1, mCallbackCount); - } - - @Test - public void testBroadcastsAfterStopAreIgnored() { - mListener.startListening(); - sendBroadcast(); - assertEquals(1, mCallbackCount); - mListener.stopListening(); - - for (int i = 0; i < 5; i++) { - sendBroadcast(); - assertEquals(1, mCallbackCount); - } - } -} diff --git a/tests/net/java/com/android/internal/net/VpnProfileTest.java b/tests/net/java/com/android/internal/net/VpnProfileTest.java new file mode 100644 index 000000000000..e5daa71c30ea --- /dev/null +++ b/tests/net/java/com/android/internal/net/VpnProfileTest.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net; + +import static com.android.testutils.ParcelUtilsKt.assertParcelSane; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.net.IpSecAlgorithm; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** Unit tests for {@link VpnProfile}. */ +@SmallTest +@RunWith(JUnit4.class) +public class VpnProfileTest { + private static final String DUMMY_PROFILE_KEY = "Test"; + + private static final int ENCODED_INDEX_AUTH_PARAMS_INLINE = 23; + private static final int ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS = 24; + + @Test + public void testDefaults() throws Exception { + final VpnProfile p = new VpnProfile(DUMMY_PROFILE_KEY); + + assertEquals(DUMMY_PROFILE_KEY, p.key); + assertEquals("", p.name); + assertEquals(VpnProfile.TYPE_PPTP, p.type); + assertEquals("", p.server); + assertEquals("", p.username); + assertEquals("", p.password); + assertEquals("", p.dnsServers); + assertEquals("", p.searchDomains); + assertEquals("", p.routes); + assertTrue(p.mppe); + assertEquals("", p.l2tpSecret); + assertEquals("", p.ipsecIdentifier); + assertEquals("", p.ipsecSecret); + assertEquals("", p.ipsecUserCert); + assertEquals("", p.ipsecCaCert); + assertEquals("", p.ipsecServerCert); + assertEquals(null, p.proxy); + assertTrue(p.getAllowedAlgorithms() != null && p.getAllowedAlgorithms().isEmpty()); + assertFalse(p.isBypassable); + assertFalse(p.isMetered); + assertEquals(1360, p.maxMtu); + assertFalse(p.areAuthParamsInline); + assertFalse(p.isRestrictedToTestNetworks); + } + + private VpnProfile getSampleIkev2Profile(String key) { + final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */); + + p.name = "foo"; + p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS; + p.server = "bar"; + p.username = "baz"; + p.password = "qux"; + p.dnsServers = "8.8.8.8"; + p.searchDomains = ""; + p.routes = "0.0.0.0/0"; + p.mppe = false; + p.l2tpSecret = ""; + p.ipsecIdentifier = "quux"; + p.ipsecSecret = "quuz"; + p.ipsecUserCert = "corge"; + p.ipsecCaCert = "grault"; + p.ipsecServerCert = "garply"; + p.proxy = null; + p.setAllowedAlgorithms( + Arrays.asList( + IpSecAlgorithm.AUTH_CRYPT_AES_GCM, + IpSecAlgorithm.AUTH_HMAC_SHA512, + IpSecAlgorithm.CRYPT_AES_CBC)); + p.isBypassable = true; + p.isMetered = true; + p.maxMtu = 1350; + p.areAuthParamsInline = true; + + // Not saved, but also not compared. + p.saveLogin = true; + + return p; + } + + @Test + public void testEquals() { + assertEquals( + getSampleIkev2Profile(DUMMY_PROFILE_KEY), getSampleIkev2Profile(DUMMY_PROFILE_KEY)); + + final VpnProfile modified = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + modified.maxMtu--; + assertNotEquals(getSampleIkev2Profile(DUMMY_PROFILE_KEY), modified); + } + + @Test + public void testParcelUnparcel() { + assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 23); + } + + @Test + public void testSetInvalidAlgorithmValueDelimiter() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + + try { + profile.setAllowedAlgorithms( + Arrays.asList("test" + VpnProfile.VALUE_DELIMITER + "test")); + fail("Expected failure due to value separator in algorithm name"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testSetInvalidAlgorithmListDelimiter() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + + try { + profile.setAllowedAlgorithms( + Arrays.asList("test" + VpnProfile.LIST_DELIMITER + "test")); + fail("Expected failure due to value separator in algorithm name"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testEncodeDecode() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode()); + assertEquals(profile, decoded); + } + + @Test + public void testEncodeDecodeTooManyValues() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + final byte[] tooManyValues = + (new String(profile.encode()) + VpnProfile.VALUE_DELIMITER + "invalid").getBytes(); + + assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooManyValues)); + } + + private String getEncodedDecodedIkev2ProfileMissingValues(int... missingIndices) { + // Sort to ensure when we remove, we can do it from greatest first. + Arrays.sort(missingIndices); + + final String encoded = new String(getSampleIkev2Profile(DUMMY_PROFILE_KEY).encode()); + final List<String> parts = + new ArrayList<>(Arrays.asList(encoded.split(VpnProfile.VALUE_DELIMITER))); + + // Remove from back first to ensure indexing is consistent. + for (int i = missingIndices.length - 1; i >= 0; i--) { + parts.remove(missingIndices[i]); + } + + return String.join(VpnProfile.VALUE_DELIMITER, parts.toArray(new String[0])); + } + + @Test + public void testEncodeDecodeInvalidNumberOfValues() { + final String tooFewValues = + getEncodedDecodedIkev2ProfileMissingValues( + ENCODED_INDEX_AUTH_PARAMS_INLINE, + ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS /* missingIndices */); + + assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes())); + } + + @Test + public void testEncodeDecodeMissingIsRestrictedToTestNetworks() { + final String tooFewValues = + getEncodedDecodedIkev2ProfileMissingValues( + ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS /* missingIndices */); + + // Verify decoding without isRestrictedToTestNetworks defaults to false + final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()); + assertFalse(decoded.isRestrictedToTestNetworks); + } + + @Test + public void testEncodeDecodeLoginsNotSaved() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + profile.saveLogin = false; + + final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode()); + assertNotEquals(profile, decoded); + + // Add the username/password back, everything else must be equal. + decoded.username = profile.username; + decoded.password = profile.password; + assertEquals(profile, decoded); + } +} diff --git a/tests/net/java/com/android/internal/util/BitUtilsTest.java b/tests/net/java/com/android/internal/util/BitUtilsTest.java index 01fb0df2d47e..d2fbdce9771a 100644 --- a/tests/net/java/com/android/internal/util/BitUtilsTest.java +++ b/tests/net/java/com/android/internal/util/BitUtilsTest.java @@ -21,11 +21,14 @@ import static com.android.internal.util.BitUtils.bytesToLEInt; import static com.android.internal.util.BitUtils.getUint16; import static com.android.internal.util.BitUtils.getUint32; import static com.android.internal.util.BitUtils.getUint8; +import static com.android.internal.util.BitUtils.packBits; import static com.android.internal.util.BitUtils.uint16; import static com.android.internal.util.BitUtils.uint32; import static com.android.internal.util.BitUtils.uint8; +import static com.android.internal.util.BitUtils.unpackBits; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -34,6 +37,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Random; @SmallTest @RunWith(AndroidJUnit4.class) @@ -110,20 +115,66 @@ public class BitUtilsTest { @Test public void testUnsignedGetters() { - ByteBuffer b = ByteBuffer.allocate(4); - b.putInt(0xffff); + ByteBuffer b = ByteBuffer.allocate(4); + b.putInt(0xffff); - assertEquals(0x0, getUint8(b, 0)); - assertEquals(0x0, getUint8(b, 1)); - assertEquals(0xff, getUint8(b, 2)); - assertEquals(0xff, getUint8(b, 3)); + assertEquals(0x0, getUint8(b, 0)); + assertEquals(0x0, getUint8(b, 1)); + assertEquals(0xff, getUint8(b, 2)); + assertEquals(0xff, getUint8(b, 3)); - assertEquals(0x0, getUint16(b, 0)); - assertEquals(0xffff, getUint16(b, 2)); + assertEquals(0x0, getUint16(b, 0)); + assertEquals(0xffff, getUint16(b, 2)); - b.rewind(); - b.putInt(0xffffffff); - assertEquals(0xffffffffL, getUint32(b, 0)); + b.rewind(); + b.putInt(0xffffffff); + assertEquals(0xffffffffL, getUint32(b, 0)); + } + + @Test + public void testBitsPacking() { + BitPackingTestCase[] testCases = { + new BitPackingTestCase(0, ints()), + new BitPackingTestCase(1, ints(0)), + new BitPackingTestCase(2, ints(1)), + new BitPackingTestCase(3, ints(0, 1)), + new BitPackingTestCase(4, ints(2)), + new BitPackingTestCase(6, ints(1, 2)), + new BitPackingTestCase(9, ints(0, 3)), + new BitPackingTestCase(~Long.MAX_VALUE, ints(63)), + new BitPackingTestCase(~Long.MAX_VALUE + 1, ints(0, 63)), + new BitPackingTestCase(~Long.MAX_VALUE + 2, ints(1, 63)), + }; + for (BitPackingTestCase tc : testCases) { + int[] got = unpackBits(tc.packedBits); + assertTrue( + "unpackBits(" + + tc.packedBits + + "): expected " + + Arrays.toString(tc.bits) + + " but got " + + Arrays.toString(got), + Arrays.equals(tc.bits, got)); + } + for (BitPackingTestCase tc : testCases) { + long got = packBits(tc.bits); + assertEquals( + "packBits(" + + Arrays.toString(tc.bits) + + "): expected " + + tc.packedBits + + " but got " + + got, + tc.packedBits, + got); + } + + long[] moreTestCases = { + 0, 1, -1, 23895, -908235, Long.MAX_VALUE, Long.MIN_VALUE, new Random().nextLong(), + }; + for (long l : moreTestCases) { + assertEquals(l, packBits(unpackBits(l))); + } } static byte[] bytes(int b1, int b2, int b3, int b4) { @@ -133,4 +184,18 @@ public class BitUtilsTest { static byte b(int i) { return (byte) i; } + + static int[] ints(int... array) { + return array; + } + + static class BitPackingTestCase { + final int[] bits; + final long packedBits; + + BitPackingTestCase(long packedBits, int[] bits) { + this.bits = bits; + this.packedBits = packedBits; + } + } } diff --git a/tests/net/java/com/android/internal/util/RingBufferTest.java b/tests/net/java/com/android/internal/util/RingBufferTest.java index eff334f7979d..d06095a690cf 100644 --- a/tests/net/java/com/android/internal/util/RingBufferTest.java +++ b/tests/net/java/com/android/internal/util/RingBufferTest.java @@ -16,6 +16,7 @@ package com.android.internal.util; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; @@ -25,9 +26,6 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.Arrays; -import java.util.Objects; - @SmallTest @RunWith(AndroidJUnit4.class) public class RingBufferTest { @@ -36,7 +34,7 @@ public class RingBufferTest { public void testEmptyRingBuffer() { RingBuffer<String> buffer = new RingBuffer<>(String.class, 100); - assertArraysEqual(new String[0], buffer.toArray()); + assertArrayEquals(new String[0], buffer.toArray()); } @Test @@ -65,7 +63,7 @@ public class RingBufferTest { buffer.append("e"); String[] expected = {"a", "b", "c", "d", "e"}; - assertArraysEqual(expected, buffer.toArray()); + assertArrayEquals(expected, buffer.toArray()); } @Test @@ -73,19 +71,19 @@ public class RingBufferTest { RingBuffer<String> buffer = new RingBuffer<>(String.class, 1); buffer.append("a"); - assertArraysEqual(new String[]{"a"}, buffer.toArray()); + assertArrayEquals(new String[]{"a"}, buffer.toArray()); buffer.append("b"); - assertArraysEqual(new String[]{"b"}, buffer.toArray()); + assertArrayEquals(new String[]{"b"}, buffer.toArray()); buffer.append("c"); - assertArraysEqual(new String[]{"c"}, buffer.toArray()); + assertArrayEquals(new String[]{"c"}, buffer.toArray()); buffer.append("d"); - assertArraysEqual(new String[]{"d"}, buffer.toArray()); + assertArrayEquals(new String[]{"d"}, buffer.toArray()); buffer.append("e"); - assertArraysEqual(new String[]{"e"}, buffer.toArray()); + assertArrayEquals(new String[]{"e"}, buffer.toArray()); } @Test @@ -100,7 +98,7 @@ public class RingBufferTest { buffer.append("e"); String[] expected1 = {"a", "b", "c", "d", "e"}; - assertArraysEqual(expected1, buffer.toArray()); + assertArrayEquals(expected1, buffer.toArray()); String[] expected2 = new String[capacity]; int firstIndex = 0; @@ -111,22 +109,22 @@ public class RingBufferTest { buffer.append("x"); expected2[i] = "x"; } - assertArraysEqual(expected2, buffer.toArray()); + assertArrayEquals(expected2, buffer.toArray()); buffer.append("x"); expected2[firstIndex] = "x"; - assertArraysEqual(expected2, buffer.toArray()); + assertArrayEquals(expected2, buffer.toArray()); for (int i = 0; i < 10; i++) { for (String s : expected2) { buffer.append(s); } } - assertArraysEqual(expected2, buffer.toArray()); + assertArrayEquals(expected2, buffer.toArray()); buffer.append("a"); expected2[lastIndex] = "a"; - assertArraysEqual(expected2, buffer.toArray()); + assertArrayEquals(expected2, buffer.toArray()); } @Test @@ -143,7 +141,7 @@ public class RingBufferTest { expected[i] = new DummyClass1(); expected[i].x = capacity * i; } - assertArraysEqual(expected, buffer.toArray()); + assertArrayEquals(expected, buffer.toArray()); for (int i = 0; i < capacity; ++i) { if (actual[i] != buffer.getNextSlot()) { @@ -177,18 +175,4 @@ public class RingBufferTest { } private static final class DummyClass3 {} - - static <T> void assertArraysEqual(T[] expected, T[] got) { - if (expected.length != got.length) { - fail(Arrays.toString(expected) + " and " + Arrays.toString(got) - + " did not have the same length"); - } - - for (int i = 0; i < expected.length; i++) { - if (!Objects.equals(expected[i], got[i])) { - fail(Arrays.toString(expected) + " and " + Arrays.toString(got) - + " were not equal"); - } - } - } } diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 945fcfa25274..e9301d1dea9d 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -21,8 +21,12 @@ import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS; import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED; import static android.content.pm.PackageManager.GET_PERMISSIONS; import static android.content.pm.PackageManager.MATCH_ANY_USER; +import static android.content.pm.PackageManager.PERMISSION_DENIED; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; +import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO; +import static android.net.ConnectivityManager.EXTRA_NETWORK_TYPE; import static android.net.ConnectivityManager.NETID_UNSET; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; @@ -31,13 +35,14 @@ import static android.net.ConnectivityManager.TYPE_ETHERNET; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA; import static android.net.ConnectivityManager.TYPE_MOBILE_MMS; -import static android.net.ConnectivityManager.TYPE_NONE; +import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL; import static android.net.ConnectivityManager.TYPE_VPN; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS; import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK; import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP; import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS; +import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS; import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL; import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID; import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; @@ -71,12 +76,20 @@ import static android.net.NetworkPolicyManager.RULE_NONE; import static android.net.NetworkPolicyManager.RULE_REJECT_ALL; import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; import static android.net.RouteInfo.RTN_UNREACHABLE; +import static android.os.Process.INVALID_UID; +import static android.system.OsConstants.IPPROTO_TCP; + +import static com.android.server.ConnectivityServiceTestUtilsKt.transportToLegacyType; +import static com.android.testutils.ConcurrentUtilsKt.await; +import static com.android.testutils.ConcurrentUtilsKt.durationOf; +import static com.android.testutils.ExceptionUtils.ignoreExceptions; +import static com.android.testutils.HandlerUtilsKt.waitForIdleSerialExecutor; +import static com.android.testutils.MiscAssertsKt.assertContainsExactly; +import static com.android.testutils.MiscAssertsKt.assertEmpty; +import static com.android.testutils.MiscAssertsKt.assertLength; +import static com.android.testutils.MiscAssertsKt.assertRunsInAtMost; +import static com.android.testutils.MiscAssertsKt.assertThrows; -import static com.android.internal.util.TestUtils.waitForIdleHandler; -import static com.android.internal.util.TestUtils.waitForIdleLooper; -import static com.android.internal.util.TestUtils.waitForIdleSerialExecutor; - -import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -84,13 +97,19 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.ArgumentMatchers.startsWith; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.any; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -102,7 +121,10 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import android.Manifest; import android.annotation.NonNull; +import android.app.AlarmManager; +import android.app.AppOpsManager; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -116,19 +138,26 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.content.res.Resources; +import android.location.LocationManager; +import android.net.CaptivePortalData; +import android.net.ConnectionInfo; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.ConnectivityManager.PacketKeepalive; import android.net.ConnectivityManager.PacketKeepaliveCallback; import android.net.ConnectivityManager.TooManyRequestsException; import android.net.ConnectivityThread; +import android.net.DataStallReportParcelable; +import android.net.IConnectivityDiagnosticsCallback; import android.net.IDnsResolver; +import android.net.IIpConnectivityMetrics; import android.net.INetd; import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; +import android.net.InetAddresses; import android.net.InterfaceConfiguration; import android.net.IpPrefix; import android.net.IpSecManager; @@ -138,34 +167,40 @@ import android.net.LinkProperties; import android.net.MatchAllNetworkSpecifier; import android.net.Network; import android.net.NetworkAgent; +import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkFactory; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; -import android.net.NetworkMisc; import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.net.NetworkStack; import android.net.NetworkStackClient; import android.net.NetworkState; +import android.net.NetworkTestResultParcelable; import android.net.NetworkUtils; import android.net.ProxyInfo; import android.net.ResolverParamsParcel; import android.net.RouteInfo; +import android.net.RouteInfoParcel; import android.net.SocketKeepalive; import android.net.UidRange; +import android.net.Uri; +import android.net.VpnManager; import android.net.metrics.IpConnectivityLog; import android.net.shared.NetworkMonitorUtils; import android.net.shared.PrivateDnsConfig; import android.net.util.MultinetworkPolicyTracker; +import android.os.BadParcelableException; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; +import android.os.IBinder; import android.os.INetworkManagementService; import android.os.Looper; -import android.os.Message; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; @@ -175,7 +210,9 @@ import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.security.KeyStore; import android.system.Os; +import android.telephony.TelephonyManager; import android.test.mock.MockContentResolver; import android.text.TextUtils; import android.util.ArraySet; @@ -183,27 +220,33 @@ import android.util.Log; import android.util.SparseArray; import androidx.test.InstrumentationRegistry; +import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.app.IBatteryStats; import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnInfo; import com.android.internal.util.ArrayUtils; import com.android.internal.util.WakeupMessage; import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.internal.util.test.FakeSettingsProvider; +import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo; import com.android.server.connectivity.ConnectivityConstants; import com.android.server.connectivity.DefaultNetworkMetrics; import com.android.server.connectivity.IpConnectivityMetrics; import com.android.server.connectivity.MockableSystemProperties; import com.android.server.connectivity.Nat464Xlat; +import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; import com.android.server.connectivity.ProxyTracker; -import com.android.server.connectivity.Tethering; import com.android.server.connectivity.Vpn; import com.android.server.net.NetworkPinner; import com.android.server.net.NetworkPolicyManagerInternal; -import com.android.server.net.NetworkStatsFactory; +import com.android.testutils.ExceptionUtils; +import com.android.testutils.HandlerUtilsKt; +import com.android.testutils.RecorderCallback.CallbackEntry; +import com.android.testutils.TestableNetworkCallback; import org.junit.After; import org.junit.Before; @@ -224,23 +267,29 @@ import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; -import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; +import java.util.function.Supplier; + +import kotlin.reflect.KClass; /** * Tests for {@link ConnectivityService}. @@ -254,36 +303,53 @@ public class ConnectivityServiceTest { private static final String TAG = "ConnectivityServiceTest"; private static final int TIMEOUT_MS = 500; - private static final int TEST_LINGER_DELAY_MS = 250; + private static final int TEST_LINGER_DELAY_MS = 300; // Chosen to be less than the linger timeout. This ensures that we can distinguish between a // LOST callback that arrives immediately and a LOST callback that arrives after the linger // timeout. For this, our assertions should run fast enough to leave less than // (mService.mLingerDelayMs - TEST_CALLBACK_TIMEOUT_MS) between the time callbacks are // supposedly fired, and the time we call expectCallback. - private final static int TEST_CALLBACK_TIMEOUT_MS = 200; + private static final int TEST_CALLBACK_TIMEOUT_MS = 250; // Chosen to be less than TEST_CALLBACK_TIMEOUT_MS. This ensures that requests have time to // complete before callbacks are verified. - private final static int TEST_REQUEST_TIMEOUT_MS = 150; + private static final int TEST_REQUEST_TIMEOUT_MS = 150; + + private static final int UNREASONABLY_LONG_ALARM_WAIT_MS = 1000; + + private static final long TIMESTAMP = 1234L; + + private static final int NET_ID = 110; private static final String CLAT_PREFIX = "v4-"; private static final String MOBILE_IFNAME = "test_rmnet_data0"; private static final String WIFI_IFNAME = "test_wlan0"; + private static final String WIFI_WOL_IFNAME = "test_wlan_wol"; + private static final String VPN_IFNAME = "tun10042"; + private static final String TEST_PACKAGE_NAME = "com.android.test.package"; private static final String[] EMPTY_STRING_ARRAY = new String[0]; + private static final String INTERFACE_NAME = "interface"; + private MockContext mServiceContext; - private WrappedConnectivityService mService; + private HandlerThread mCsHandlerThread; + private ConnectivityService mService; private WrappedConnectivityManager mCm; - private MockNetworkAgent mWiFiNetworkAgent; - private MockNetworkAgent mCellNetworkAgent; - private MockNetworkAgent mEthernetNetworkAgent; + private TestNetworkAgentWrapper mWiFiNetworkAgent; + private TestNetworkAgentWrapper mCellNetworkAgent; + private TestNetworkAgentWrapper mEthernetNetworkAgent; private MockVpn mMockVpn; private Context mContext; private INetworkPolicyListener mPolicyListener; + private WrappedMultinetworkPolicyTracker mPolicyTracker; + private HandlerThread mAlarmManagerThread; + private TestNetIdManager mNetIdManager; + @Mock IIpConnectivityMetrics mIpConnectivityMetrics; @Mock IpConnectivityMetrics.Logger mMetricsService; @Mock DefaultNetworkMetrics mDefaultNetworkMetrics; @Mock INetworkManagementService mNetworkManagementService; @Mock INetworkStatsService mStatsService; + @Mock IBatteryStats mBatteryStatsService; @Mock INetworkPolicyManager mNpm; @Mock IDnsResolver mMockDnsResolver; @Mock INetd mMockNetd; @@ -291,6 +357,12 @@ public class ConnectivityServiceTest { @Mock PackageManager mPackageManager; @Mock UserManager mUserManager; @Mock NotificationManager mNotificationManager; + @Mock AlarmManager mAlarmManager; + @Mock IConnectivityDiagnosticsCallback mConnectivityDiagnosticsCallback; + @Mock IBinder mIBinder; + @Mock LocationManager mLocationManager; + @Mock AppOpsManager mAppOpsManager; + @Mock TelephonyManager mTelephonyManager; private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor = ArgumentCaptor.forClass(ResolverParamsParcel.class); @@ -320,6 +392,8 @@ public class ConnectivityServiceTest { @Spy private Resources mResources; private final LinkedBlockingQueue<Intent> mStartedActivities = new LinkedBlockingQueue<>(); + // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant + private final HashMap<String, Integer> mMockedPermissions = new HashMap<>(); MockContext(Context base, ContentProvider settingsProvider) { super(base); @@ -330,6 +404,13 @@ public class ConnectivityServiceTest { "wifi,1,1,1,-1,true", "mobile,0,0,0,-1,true", "mobile_mms,2,0,2,60000,true", + "mobile_supl,3,0,2,60000,true", + }); + + when(mResources.getStringArray( + com.android.internal.R.array.config_wakeonlan_supported_interfaces)) + .thenReturn(new String[]{ + WIFI_WOL_IFNAME, }); mContentResolver = new MockContentResolver(); @@ -361,8 +442,11 @@ public class ConnectivityServiceTest { public Object getSystemService(String name) { if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm; if (Context.NOTIFICATION_SERVICE.equals(name)) return mNotificationManager; - if (Context.NETWORK_STACK_SERVICE.equals(name)) return mNetworkStack; if (Context.USER_SERVICE.equals(name)) return mUserManager; + if (Context.ALARM_SERVICE.equals(name)) return mAlarmManager; + if (Context.LOCATION_SERVICE.equals(name)) return mLocationManager; + if (Context.APP_OPS_SERVICE.equals(name)) return mAppOpsManager; + if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager; return super.getSystemService(name); } @@ -381,40 +465,67 @@ public class ConnectivityServiceTest { return mPackageManager; } + private int checkMockedPermission(String permission, Supplier<Integer> ifAbsent) { + final Integer granted = mMockedPermissions.get(permission); + return granted != null ? granted : ifAbsent.get(); + } + + @Override + public int checkPermission(String permission, int pid, int uid) { + return checkMockedPermission( + permission, () -> super.checkPermission(permission, pid, uid)); + } + + @Override + public int checkCallingOrSelfPermission(String permission) { + return checkMockedPermission( + permission, () -> super.checkCallingOrSelfPermission(permission)); + } + @Override public void enforceCallingOrSelfPermission(String permission, String message) { - // The mainline permission can only be held if signed with the network stack certificate - // Skip testing for this permission. - if (NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK.equals(permission)) return; - // All other permissions should be held by the test or unnecessary: check as normal to - // make sure the code does not rely on unexpected permissions. - super.enforceCallingOrSelfPermission(permission, message); + final Integer granted = mMockedPermissions.get(permission); + if (granted == null) { + super.enforceCallingOrSelfPermission(permission, message); + return; + } + + if (!granted.equals(PERMISSION_GRANTED)) { + throw new SecurityException("[Test] permission denied: " + permission); + } + } + + /** + * Mock checks for the specified permission, and have them behave as per {@code granted}. + * + * <p>Passing null reverts to default behavior, which does a real permission check on the + * test package. + * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or + * {@link PackageManager#PERMISSION_DENIED}. + */ + public void setPermission(String permission, Integer granted) { + mMockedPermissions.put(permission, granted); } } - public void waitForIdle(int timeoutMsAsInt) { - long timeoutMs = timeoutMsAsInt; - waitForIdleHandler(mService.mHandlerThread, timeoutMs); - waitForIdle(mCellNetworkAgent, timeoutMs); - waitForIdle(mWiFiNetworkAgent, timeoutMs); - waitForIdle(mEthernetNetworkAgent, timeoutMs); - waitForIdleHandler(mService.mHandlerThread, timeoutMs); - waitForIdleLooper(ConnectivityThread.getInstanceLooper(), timeoutMs); + private void waitForIdle() { + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + waitForIdle(mCellNetworkAgent, TIMEOUT_MS); + waitForIdle(mWiFiNetworkAgent, TIMEOUT_MS); + waitForIdle(mEthernetNetworkAgent, TIMEOUT_MS); + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + HandlerUtilsKt.waitForIdle(ConnectivityThread.get(), TIMEOUT_MS); } - public void waitForIdle(MockNetworkAgent agent, long timeoutMs) { + private void waitForIdle(TestNetworkAgentWrapper agent, long timeoutMs) { if (agent == null) { return; } - waitForIdleHandler(agent.mHandlerThread, timeoutMs); - } - - private void waitForIdle() { - waitForIdle(TIMEOUT_MS); + agent.waitForIdle(timeoutMs); } @Test - public void testWaitForIdle() { + public void testWaitForIdle() throws Exception { final int attempts = 50; // Causes the test to take about 200ms on bullhead-eng. // Tests that waitForIdle returns immediately if the service is already idle. @@ -423,10 +534,10 @@ public class ConnectivityServiceTest { } // Bring up a network that we can use to send messages to ConnectivityService. - ConditionVariable cv = waitForConnectivityBroadcasts(1); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); - waitFor(cv); + b.expectBroadcast(); Network n = mWiFiNetworkAgent.getNetwork(); assertNotNull(n); @@ -441,12 +552,12 @@ public class ConnectivityServiceTest { // This test has an inherent race condition in it, and cannot be enabled for continuous testing // or presubmit tests. It is kept for manual runs and documentation purposes. @Ignore - public void verifyThatNotWaitingForIdleCausesRaceConditions() { + public void verifyThatNotWaitingForIdleCausesRaceConditions() throws Exception { // Bring up a network that we can use to send messages to ConnectivityService. - ConditionVariable cv = waitForConnectivityBroadcasts(1); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); - waitFor(cv); + b.expectBroadcast(); Network n = mWiFiNetworkAgent.getNetwork(); assertNotNull(n); @@ -464,108 +575,57 @@ public class ConnectivityServiceTest { fail("expected race condition at least once in " + attempts + " attempts"); } - private class MockNetworkAgent { - private static final int VALIDATION_RESULT_BASE = NETWORK_VALIDATION_PROBE_DNS - | NETWORK_VALIDATION_PROBE_HTTP - | NETWORK_VALIDATION_PROBE_HTTPS; - private static final int VALIDATION_RESULT_VALID = VALIDATION_RESULT_BASE - | NETWORK_VALIDATION_RESULT_VALID; - private static final int VALIDATION_RESULT_PARTIAL = VALIDATION_RESULT_BASE - | NETWORK_VALIDATION_PROBE_FALLBACK - | NETWORK_VALIDATION_RESULT_PARTIAL; + private class TestNetworkAgentWrapper extends NetworkAgentWrapper { private static final int VALIDATION_RESULT_INVALID = 0; - private final INetworkMonitor mNetworkMonitor; - private final NetworkInfo mNetworkInfo; - private final NetworkCapabilities mNetworkCapabilities; - private final HandlerThread mHandlerThread; - private final ConditionVariable mDisconnected = new ConditionVariable(); - private final ConditionVariable mNetworkStatusReceived = new ConditionVariable(); - private final ConditionVariable mPreventReconnectReceived = new ConditionVariable(); - private int mScore; - private NetworkAgent mNetworkAgent; - private int mStartKeepaliveError = SocketKeepalive.ERROR_UNSUPPORTED; - private int mStopKeepaliveError = SocketKeepalive.NO_KEEPALIVE; - private Integer mExpectedKeepaliveSlot = null; - // Contains the redirectUrl from networkStatus(). Before reading, wait for - // mNetworkStatusReceived. - private String mRedirectUrl; + private static final long DATA_STALL_TIMESTAMP = 10L; + private static final int DATA_STALL_DETECTION_METHOD = 1; + private INetworkMonitor mNetworkMonitor; private INetworkMonitorCallbacks mNmCallbacks; - private int mNmValidationResult = VALIDATION_RESULT_BASE; + private int mNmValidationResult = VALIDATION_RESULT_INVALID; + private int mProbesCompleted; + private int mProbesSucceeded; private String mNmValidationRedirectUrl = null; private boolean mNmProvNotificationRequested = false; - void setNetworkValid() { - mNmValidationResult = VALIDATION_RESULT_VALID; - mNmValidationRedirectUrl = null; - } + private final ConditionVariable mNetworkStatusReceived = new ConditionVariable(); + // Contains the redirectUrl from networkStatus(). Before reading, wait for + // mNetworkStatusReceived. + private String mRedirectUrl; - void setNetworkInvalid() { - mNmValidationResult = VALIDATION_RESULT_INVALID; - mNmValidationRedirectUrl = null; + TestNetworkAgentWrapper(int transport) throws Exception { + this(transport, new LinkProperties(), null); } - void setNetworkPortal(String redirectUrl) { - setNetworkInvalid(); - mNmValidationRedirectUrl = redirectUrl; + TestNetworkAgentWrapper(int transport, LinkProperties linkProperties) + throws Exception { + this(transport, linkProperties, null); } - void setNetworkPartial() { - mNmValidationResult = VALIDATION_RESULT_PARTIAL; - mNmValidationRedirectUrl = null; - } + private TestNetworkAgentWrapper(int transport, LinkProperties linkProperties, + NetworkCapabilities ncTemplate) throws Exception { + super(transport, linkProperties, ncTemplate, mServiceContext); - void setNetworkPartialValid() { - mNmValidationResult = VALIDATION_RESULT_PARTIAL | VALIDATION_RESULT_VALID; - mNmValidationRedirectUrl = null; + // Waits for the NetworkAgent to be registered, which includes the creation of the + // NetworkMonitor. + waitForIdle(TIMEOUT_MS); + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + HandlerUtilsKt.waitForIdle(ConnectivityThread.get(), TIMEOUT_MS); } - MockNetworkAgent(int transport) { - this(transport, new LinkProperties()); - } - - MockNetworkAgent(int transport, LinkProperties linkProperties) { - final int type = transportToLegacyType(transport); - final String typeName = ConnectivityManager.getNetworkTypeName(type); - mNetworkInfo = new NetworkInfo(type, 0, typeName, "Mock"); - mNetworkCapabilities = new NetworkCapabilities(); - mNetworkCapabilities.addTransportType(transport); - switch (transport) { - case TRANSPORT_ETHERNET: - mScore = 70; - break; - case TRANSPORT_WIFI: - mScore = 60; - break; - case TRANSPORT_CELLULAR: - mScore = 50; - break; - case TRANSPORT_WIFI_AWARE: - mScore = 20; - break; - case TRANSPORT_VPN: - mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_VPN); - mScore = ConnectivityConstants.VPN_DEFAULT_SCORE; - break; - default: - throw new UnsupportedOperationException("unimplemented network type"); - } - mHandlerThread = new HandlerThread("Mock-" + typeName); - mHandlerThread.start(); - + @Override + protected InstrumentedNetworkAgent makeNetworkAgent(LinkProperties linkProperties) + throws Exception { mNetworkMonitor = mock(INetworkMonitor.class); + final Answer validateAnswer = inv -> { - new Thread(this::onValidationRequested).start(); + new Thread(ignoreExceptions(this::onValidationRequested)).start(); return null; }; - try { - doAnswer(validateAnswer).when(mNetworkMonitor).notifyNetworkConnected(any(), any()); - doAnswer(validateAnswer).when(mNetworkMonitor).forceReevaluation(anyInt()); - } catch (RemoteException e) { - fail(e.getMessage()); - } + doAnswer(validateAnswer).when(mNetworkMonitor).notifyNetworkConnected(any(), any()); + doAnswer(validateAnswer).when(mNetworkMonitor).forceReevaluation(anyInt()); final ArgumentCaptor<Network> nmNetworkCaptor = ArgumentCaptor.forClass(Network.class); final ArgumentCaptor<INetworkMonitorCallbacks> nmCbCaptor = @@ -575,132 +635,50 @@ public class ConnectivityServiceTest { any() /* name */, nmCbCaptor.capture()); - mNetworkAgent = new NetworkAgent(mHandlerThread.getLooper(), mServiceContext, - "Mock-" + typeName, mNetworkInfo, mNetworkCapabilities, - linkProperties, mScore, new NetworkMisc(), NetworkFactory.SerialNumber.NONE) { - @Override - public void unwanted() { mDisconnected.open(); } - - @Override - public void startSocketKeepalive(Message msg) { - int slot = msg.arg1; - if (mExpectedKeepaliveSlot != null) { - assertEquals((int) mExpectedKeepaliveSlot, slot); - } - onSocketKeepaliveEvent(slot, mStartKeepaliveError); - } - - @Override - public void stopSocketKeepalive(Message msg) { - onSocketKeepaliveEvent(msg.arg1, mStopKeepaliveError); - } - + final InstrumentedNetworkAgent na = new InstrumentedNetworkAgent(this, linkProperties) { @Override public void networkStatus(int status, String redirectUrl) { mRedirectUrl = redirectUrl; mNetworkStatusReceived.open(); } - - @Override - protected void preventAutomaticReconnect() { - mPreventReconnectReceived.open(); - } - - @Override - protected void addKeepalivePacketFilter(Message msg) { - Log.i(TAG, "Add keepalive packet filter."); - } - - @Override - protected void removeKeepalivePacketFilter(Message msg) { - Log.i(TAG, "Remove keepalive packet filter."); - } }; - assertEquals(mNetworkAgent.netId, nmNetworkCaptor.getValue().netId); + assertEquals(na.getNetwork().netId, nmNetworkCaptor.getValue().netId); mNmCallbacks = nmCbCaptor.getValue(); - try { - mNmCallbacks.onNetworkMonitorCreated(mNetworkMonitor); - } catch (RemoteException e) { - fail(e.getMessage()); - } + mNmCallbacks.onNetworkMonitorCreated(mNetworkMonitor); - // Waits for the NetworkAgent to be registered, which includes the creation of the - // NetworkMonitor. - waitForIdle(); + return na; } - private void onValidationRequested() { - try { - if (mNmProvNotificationRequested - && ((mNmValidationResult & NETWORK_VALIDATION_RESULT_VALID) != 0)) { - mNmCallbacks.hideProvisioningNotification(); - mNmProvNotificationRequested = false; - } - - mNmCallbacks.notifyNetworkTested( - mNmValidationResult, mNmValidationRedirectUrl); - - if (mNmValidationRedirectUrl != null) { - mNmCallbacks.showProvisioningNotification( - "test_provisioning_notif_action", "com.android.test.package"); - mNmProvNotificationRequested = true; - } - } catch (RemoteException e) { - fail(e.getMessage()); + private void onValidationRequested() throws Exception { + if (mNmProvNotificationRequested + && ((mNmValidationResult & NETWORK_VALIDATION_RESULT_VALID) != 0)) { + mNmCallbacks.hideProvisioningNotification(); + mNmProvNotificationRequested = false; } - } - - public void adjustScore(int change) { - mScore += change; - mNetworkAgent.sendNetworkScore(mScore); - } - - public int getScore() { - return mScore; - } - - public void explicitlySelected(boolean acceptUnvalidated) { - mNetworkAgent.explicitlySelected(acceptUnvalidated); - } - - public void addCapability(int capability) { - mNetworkCapabilities.addCapability(capability); - mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); - } - - public void removeCapability(int capability) { - mNetworkCapabilities.removeCapability(capability); - mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); - } - - public void setUids(Set<UidRange> uids) { - mNetworkCapabilities.setUids(uids); - mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); - } - public void setSignalStrength(int signalStrength) { - mNetworkCapabilities.setSignalStrength(signalStrength); - mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); - } - - public void setNetworkSpecifier(NetworkSpecifier networkSpecifier) { - mNetworkCapabilities.setNetworkSpecifier(networkSpecifier); - mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); - } - - public void setNetworkCapabilities(NetworkCapabilities nc, - boolean sendToConnectivityService) { - mNetworkCapabilities.set(nc); - if (sendToConnectivityService) { - mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); + mNmCallbacks.notifyProbeStatusChanged(mProbesCompleted, mProbesSucceeded); + final NetworkTestResultParcelable p = new NetworkTestResultParcelable(); + p.result = mNmValidationResult; + p.probesAttempted = mProbesCompleted; + p.probesSucceeded = mProbesSucceeded; + p.redirectUrl = mNmValidationRedirectUrl; + p.timestampMillis = TIMESTAMP; + mNmCallbacks.notifyNetworkTestedWithExtras(p); + + if (mNmValidationRedirectUrl != null) { + mNmCallbacks.showProvisioningNotification( + "test_provisioning_notif_action", TEST_PACKAGE_NAME); + mNmProvNotificationRequested = true; } } + /** + * Connect without adding any internet capability. + */ public void connectWithoutInternet() { - mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null); - mNetworkAgent.sendNetworkInfo(mNetworkInfo); + super.connect(); } /** @@ -708,7 +686,7 @@ public class ConnectivityServiceTest { * @param validated Indicate if network should pretend to be validated. */ public void connect(boolean validated) { - connect(validated, true); + connect(validated, true, false /* isStrictMode */); } /** @@ -716,24 +694,22 @@ public class ConnectivityServiceTest { * @param validated Indicate if network should pretend to be validated. * @param hasInternet Indicate if network should pretend to have NET_CAPABILITY_INTERNET. */ - public void connect(boolean validated, boolean hasInternet) { - assertEquals("MockNetworkAgents can only be connected once", - mNetworkInfo.getDetailedState(), DetailedState.IDLE); - assertFalse(mNetworkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)); + public void connect(boolean validated, boolean hasInternet, boolean isStrictMode) { + assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_INTERNET)); - NetworkCallback callback = null; + ConnectivityManager.NetworkCallback callback = null; final ConditionVariable validatedCv = new ConditionVariable(); if (validated) { - setNetworkValid(); + setNetworkValid(isStrictMode); NetworkRequest request = new NetworkRequest.Builder() - .addTransportType(mNetworkCapabilities.getTransportTypes()[0]) + .addTransportType(getNetworkCapabilities().getTransportTypes()[0]) .clearCapabilities() .build(); - callback = new NetworkCallback() { + callback = new ConnectivityManager.NetworkCallback() { public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) { if (network.equals(getNetwork()) && - networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) { + networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) { validatedCv.open(); } } @@ -749,15 +725,15 @@ public class ConnectivityServiceTest { if (validated) { // Wait for network to validate. waitFor(validatedCv); - setNetworkInvalid(); + setNetworkInvalid(isStrictMode); } if (callback != null) mCm.unregisterNetworkCallback(callback); } - public void connectWithCaptivePortal(String redirectUrl) { - setNetworkPortal(redirectUrl); - connect(false); + public void connectWithCaptivePortal(String redirectUrl, boolean isStrictMode) { + setNetworkPortal(redirectUrl, isStrictMode); + connect(false, true /* hasInternet */, isStrictMode); } public void connectWithPartialConnectivity() { @@ -765,47 +741,86 @@ public class ConnectivityServiceTest { connect(false); } - public void suspend() { - mNetworkInfo.setDetailedState(DetailedState.SUSPENDED, null, null); - mNetworkAgent.sendNetworkInfo(mNetworkInfo); - } - - public void resume() { - mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null); - mNetworkAgent.sendNetworkInfo(mNetworkInfo); + public void connectWithPartialValidConnectivity(boolean isStrictMode) { + setNetworkPartialValid(isStrictMode); + connect(false, true /* hasInternet */, isStrictMode); } - public void disconnect() { - mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null); - mNetworkAgent.sendNetworkInfo(mNetworkInfo); - } - - public Network getNetwork() { - return new Network(mNetworkAgent.netId); + void setNetworkValid(boolean isStrictMode) { + mNmValidationResult = NETWORK_VALIDATION_RESULT_VALID; + mNmValidationRedirectUrl = null; + int probesSucceeded = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS; + if (isStrictMode) { + probesSucceeded |= NETWORK_VALIDATION_PROBE_PRIVDNS; + } + // The probesCompleted equals to probesSucceeded for the case of valid network, so put + // the same value into two different parameter of the method. + setProbesStatus(probesSucceeded, probesSucceeded); } - public ConditionVariable getPreventReconnectReceived() { - return mPreventReconnectReceived; + void setNetworkInvalid(boolean isStrictMode) { + mNmValidationResult = VALIDATION_RESULT_INVALID; + mNmValidationRedirectUrl = null; + int probesCompleted = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS + | NETWORK_VALIDATION_PROBE_HTTP; + int probesSucceeded = 0; + // If the isStrictMode is true, it means the network is invalid when NetworkMonitor + // tried to validate the private DNS but failed. + if (isStrictMode) { + probesCompleted &= ~NETWORK_VALIDATION_PROBE_HTTP; + probesSucceeded = probesCompleted; + probesCompleted |= NETWORK_VALIDATION_PROBE_PRIVDNS; + } + setProbesStatus(probesCompleted, probesSucceeded); } - public ConditionVariable getDisconnectedCV() { - return mDisconnected; + void setNetworkPortal(String redirectUrl, boolean isStrictMode) { + setNetworkInvalid(isStrictMode); + mNmValidationRedirectUrl = redirectUrl; + // Suppose the portal is found when NetworkMonitor probes NETWORK_VALIDATION_PROBE_HTTP + // in the beginning, so the NETWORK_VALIDATION_PROBE_HTTPS hasn't probed yet. + int probesCompleted = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP; + int probesSucceeded = VALIDATION_RESULT_INVALID; + if (isStrictMode) { + probesCompleted |= NETWORK_VALIDATION_PROBE_PRIVDNS; + } + setProbesStatus(probesCompleted, probesSucceeded); } - public void sendLinkProperties(LinkProperties lp) { - mNetworkAgent.sendLinkProperties(lp); + void setNetworkPartial() { + mNmValidationResult = NETWORK_VALIDATION_RESULT_PARTIAL; + mNmValidationRedirectUrl = null; + int probesCompleted = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS + | NETWORK_VALIDATION_PROBE_FALLBACK; + int probesSucceeded = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_FALLBACK; + setProbesStatus(probesCompleted, probesSucceeded); } - public void setStartKeepaliveError(int error) { - mStartKeepaliveError = error; + void setNetworkPartialValid(boolean isStrictMode) { + setNetworkPartial(); + mNmValidationResult |= NETWORK_VALIDATION_RESULT_VALID; + int probesCompleted = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS + | NETWORK_VALIDATION_PROBE_HTTP; + int probesSucceeded = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP; + // Suppose the partial network cannot pass the private DNS validation as well, so only + // add NETWORK_VALIDATION_PROBE_DNS in probesCompleted but not probesSucceeded. + if (isStrictMode) { + probesCompleted |= NETWORK_VALIDATION_PROBE_PRIVDNS; + } + setProbesStatus(probesCompleted, probesSucceeded); } - public void setStopKeepaliveError(int error) { - mStopKeepaliveError = error; + void setProbesStatus(int probesCompleted, int probesSucceeded) { + mProbesCompleted = probesCompleted; + mProbesSucceeded = probesSucceeded; } - public void setExpectedKeepaliveSlot(Integer slot) { - mExpectedKeepaliveSlot = slot; + void notifyCaptivePortalDataChanged(CaptivePortalData data) { + try { + mNmCallbacks.notifyCaptivePortalDataChanged(data); + } catch (RemoteException e) { + throw new AssertionError("This cannot happen", e); + } } public String waitForRedirectUrl() { @@ -813,12 +828,19 @@ public class ConnectivityServiceTest { return mRedirectUrl; } - public NetworkAgent getNetworkAgent() { - return mNetworkAgent; + public void expectDisconnected() { + expectDisconnected(TIMEOUT_MS); } - public NetworkCapabilities getNetworkCapabilities() { - return mNetworkCapabilities; + public void expectPreventReconnectReceived() { + expectPreventReconnectReceived(TIMEOUT_MS); + } + + void notifyDataStallSuspected() throws Exception { + final DataStallReportParcelable p = new DataStallReportParcelable(); + p.detectionMethod = DATA_STALL_DETECTION_METHOD; + p.timestampMillis = DATA_STALL_TIMESTAMP; + mNmCallbacks.notifyDataStallSuspected(p); } } @@ -978,286 +1000,198 @@ public class ConnectivityServiceTest { } } + private Set<UidRange> uidRangesForUid(int uid) { + final ArraySet<UidRange> ranges = new ArraySet<>(); + ranges.add(new UidRange(uid, uid)); + return ranges; + } + private static Looper startHandlerThreadAndReturnLooper() { final HandlerThread handlerThread = new HandlerThread("MockVpnThread"); handlerThread.start(); return handlerThread.getLooper(); } - private class MockVpn extends Vpn { - // TODO : the interactions between this mock and the mock network agent are too - // hard to get right at this moment, because it's unclear in which case which - // target needs to get a method call or both, and in what order. It's because - // MockNetworkAgent wants to manage its own NetworkCapabilities, but the Vpn - // parent class of MockVpn agent wants that responsibility. - // That being said inside the test it should be possible to make the interactions - // harder to get wrong with precise speccing, judicious comments, helper methods - // and a few sprinkled assertions. - - private boolean mConnected = false; + private class MockVpn extends Vpn implements TestableNetworkCallback.HasNetwork { // Careful ! This is different from mNetworkAgent, because MockNetworkAgent does // not inherit from NetworkAgent. - private MockNetworkAgent mMockNetworkAgent; + private TestNetworkAgentWrapper mMockNetworkAgent; + private boolean mAgentRegistered = false; + + private int mVpnType = VpnManager.TYPE_VPN_SERVICE; + private VpnInfo mVpnInfo; public MockVpn(int userId) { super(startHandlerThreadAndReturnLooper(), mServiceContext, mNetworkManagementService, - userId); - } - - public void setNetworkAgent(MockNetworkAgent agent) { - waitForIdle(agent, TIMEOUT_MS); - mMockNetworkAgent = agent; - mNetworkAgent = agent.getNetworkAgent(); - mNetworkCapabilities.set(agent.getNetworkCapabilities()); + userId, mock(KeyStore.class)); + mConfig = new VpnConfig(); } public void setUids(Set<UidRange> uids) { mNetworkCapabilities.setUids(uids); - updateCapabilities(null /* defaultNetwork */); + updateCapabilitiesInternal(null /* defaultNetwork */, true); } - @Override - public int getNetId() { - if (mMockNetworkAgent == null) { - return NETID_UNSET; - } - return mMockNetworkAgent.getNetwork().netId; + public void setVpnType(int vpnType) { + mVpnType = vpnType; } @Override - public boolean appliesToUid(int uid) { - return mConnected; // Trickery to simplify testing. + public Network getNetwork() { + return (mMockNetworkAgent == null) ? null : mMockNetworkAgent.getNetwork(); } @Override - protected boolean isCallerEstablishedOwnerLocked() { - return mConnected; // Similar trickery - } - - private void connect(boolean isAlwaysMetered) { - mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities()); - mConnected = true; - mConfig = new VpnConfig(); - mConfig.isMetered = isAlwaysMetered; - } - - public void connectAsAlwaysMetered() { - connect(true /* isAlwaysMetered */); - } - - public void connect() { - connect(false /* isAlwaysMetered */); + public int getNetId() { + return (mMockNetworkAgent == null) ? NETID_UNSET : mMockNetworkAgent.getNetwork().netId; } @Override - public NetworkCapabilities updateCapabilities(Network defaultNetwork) { - if (!mConnected) return null; - super.updateCapabilities(defaultNetwork); - // Because super.updateCapabilities will update the capabilities of the agent but - // not the mock agent, the mock agent needs to know about them. - copyCapabilitiesToNetworkAgent(); - return new NetworkCapabilities(mNetworkCapabilities); + public int getActiveAppVpnType() { + return mVpnType; } - private void copyCapabilitiesToNetworkAgent() { - if (null != mMockNetworkAgent) { - mMockNetworkAgent.setNetworkCapabilities(mNetworkCapabilities, - false /* sendToConnectivityService */); - } - } - - public void disconnect() { - mConnected = false; - mConfig = null; - } - } - - private class FakeWakeupMessage extends WakeupMessage { - private static final int UNREASONABLY_LONG_WAIT = 1000; - - public FakeWakeupMessage(Context context, Handler handler, String cmdName, int cmd) { - super(context, handler, cmdName, cmd); + private void registerAgent(boolean isAlwaysMetered, Set<UidRange> uids, LinkProperties lp) + throws Exception { + if (mAgentRegistered) throw new IllegalStateException("already registered"); + setUids(uids); + mConfig.isMetered = isAlwaysMetered; + mInterface = VPN_IFNAME; + mMockNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN, lp, + mNetworkCapabilities); + mMockNetworkAgent.waitForIdle(TIMEOUT_MS); + mAgentRegistered = true; + mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities()); + mNetworkAgent = mMockNetworkAgent.getNetworkAgent(); } - public FakeWakeupMessage(Context context, Handler handler, String cmdName, int cmd, - int arg1, int arg2, Object obj) { - super(context, handler, cmdName, cmd, arg1, arg2, obj); + private void registerAgent(Set<UidRange> uids) throws Exception { + registerAgent(false /* isAlwaysMetered */, uids, new LinkProperties()); } - @Override - public void schedule(long when) { - long delayMs = when - SystemClock.elapsedRealtime(); - if (delayMs < 0) delayMs = 0; - if (delayMs > UNREASONABLY_LONG_WAIT) { - fail("Attempting to send msg more than " + UNREASONABLY_LONG_WAIT + - "ms into the future: " + delayMs); - } - Message msg = mHandler.obtainMessage(mCmd, mArg1, mArg2, mObj); - mHandler.sendMessageDelayed(msg, delayMs); + private void connect(boolean validated, boolean hasInternet, boolean isStrictMode) { + mMockNetworkAgent.connect(validated, hasInternet, isStrictMode); } - @Override - public void cancel() { - mHandler.removeMessages(mCmd, mObj); + private void connect(boolean validated) { + mMockNetworkAgent.connect(validated); } - @Override - public void onAlarm() { - throw new AssertionError("Should never happen. Update this fake."); + private TestNetworkAgentWrapper getAgent() { + return mMockNetworkAgent; } - } - private class WrappedMultinetworkPolicyTracker extends MultinetworkPolicyTracker { - public volatile boolean configRestrictsAvoidBadWifi; - public volatile int configMeteredMultipathPreference; - - public WrappedMultinetworkPolicyTracker(Context c, Handler h, Runnable r) { - super(c, h, r); + public void establish(LinkProperties lp, int uid, Set<UidRange> ranges, boolean validated, + boolean hasInternet, boolean isStrictMode) throws Exception { + mNetworkCapabilities.setOwnerUid(uid); + mNetworkCapabilities.setAdministratorUids(new int[]{uid}); + registerAgent(false, ranges, lp); + connect(validated, hasInternet, isStrictMode); + waitForIdle(); } - @Override - public boolean configRestrictsAvoidBadWifi() { - return configRestrictsAvoidBadWifi; + public void establish(LinkProperties lp, int uid, Set<UidRange> ranges) throws Exception { + establish(lp, uid, ranges, true, true, false); } - @Override - public int configMeteredMultipathPreference() { - return configMeteredMultipathPreference; + public void establishForMyUid(LinkProperties lp) throws Exception { + final int uid = Process.myUid(); + establish(lp, uid, uidRangesForUid(uid), true, true, false); } - } - - private class WrappedConnectivityService extends ConnectivityService { - public WrappedMultinetworkPolicyTracker wrappedMultinetworkPolicyTracker; - private MockableSystemProperties mSystemProperties; - public WrappedConnectivityService(Context context, INetworkManagementService netManager, - INetworkStatsService statsService, INetworkPolicyManager policyManager, - IpConnectivityLog log, INetd netd, IDnsResolver dnsResolver) { - super(context, netManager, statsService, policyManager, dnsResolver, log, netd); - mNetd = netd; - mLingerDelayMs = TEST_LINGER_DELAY_MS; + public void establishForMyUid(boolean validated, boolean hasInternet, boolean isStrictMode) + throws Exception { + final int uid = Process.myUid(); + establish(new LinkProperties(), uid, uidRangesForUid(uid), validated, hasInternet, + isStrictMode); } - @Override - protected MockableSystemProperties getSystemProperties() { - // Minimal approach to overriding system properties: let most calls fall through to real - // device values, and only override ones values that are important to this test. - mSystemProperties = spy(new MockableSystemProperties()); - when(mSystemProperties.getInt("net.tcp.default_init_rwnd", 0)).thenReturn(0); - when(mSystemProperties.getBoolean("ro.radio.noril", false)).thenReturn(false); - return mSystemProperties; + public void establishForMyUid() throws Exception { + establishForMyUid(new LinkProperties()); } - @Override - protected Tethering makeTethering() { - return mock(Tethering.class); + public void sendLinkProperties(LinkProperties lp) { + mMockNetworkAgent.sendLinkProperties(lp); } - @Override - protected ProxyTracker makeProxyTracker() { - return mock(ProxyTracker.class); + private NetworkCapabilities updateCapabilitiesInternal(Network defaultNetwork, + boolean sendToConnectivityService) { + if (!mAgentRegistered) return null; + super.updateCapabilities(defaultNetwork); + // Because super.updateCapabilities will update the capabilities of the agent but + // not the mock agent, the mock agent needs to know about them. + copyCapabilitiesToNetworkAgent(sendToConnectivityService); + return new NetworkCapabilities(mNetworkCapabilities); } - @Override - protected int reserveNetId() { - while (true) { - final int netId = super.reserveNetId(); - - // Don't overlap test NetIDs with real NetIDs as binding sockets to real networks - // can have odd side-effects, like network validations succeeding. - Context context = InstrumentationRegistry.getContext(); - final Network[] networks = ConnectivityManager.from(context).getAllNetworks(); - boolean overlaps = false; - for (Network network : networks) { - if (netId == network.netId) { - overlaps = true; - break; - } - } - if (overlaps) continue; - - return netId; + private void copyCapabilitiesToNetworkAgent(boolean sendToConnectivityService) { + if (null != mMockNetworkAgent) { + mMockNetworkAgent.setNetworkCapabilities(mNetworkCapabilities, + sendToConnectivityService); } } @Override - protected boolean queryUserAccess(int uid, int netId) { - return true; + public NetworkCapabilities updateCapabilities(Network defaultNetwork) { + return updateCapabilitiesInternal(defaultNetwork, false); } - public Nat464Xlat getNat464Xlat(MockNetworkAgent mna) { - return getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd; + public void disconnect() { + if (mMockNetworkAgent != null) mMockNetworkAgent.disconnect(); + mAgentRegistered = false; } @Override - public MultinetworkPolicyTracker createMultinetworkPolicyTracker( - Context c, Handler h, Runnable r) { - final WrappedMultinetworkPolicyTracker tracker = new WrappedMultinetworkPolicyTracker(c, h, r); - return tracker; - } - - public WrappedMultinetworkPolicyTracker getMultinetworkPolicyTracker() { - return (WrappedMultinetworkPolicyTracker) mMultinetworkPolicyTracker; - } + public synchronized VpnInfo getVpnInfo() { + if (mVpnInfo != null) return mVpnInfo; - @Override - protected NetworkStackClient getNetworkStack() { - return mNetworkStack; + return super.getVpnInfo(); } - @Override - public WakeupMessage makeWakeupMessage( - Context context, Handler handler, String cmdName, int cmd, Object obj) { - return new FakeWakeupMessage(context, handler, cmdName, cmd, 0, 0, obj); + private synchronized void setVpnInfo(VpnInfo vpnInfo) { + mVpnInfo = vpnInfo; } + } - @Override - public boolean hasService(String name) { - // Currenty, the only relevant service that ConnectivityService checks for is - // ETHERNET_SERVICE. - return Context.ETHERNET_SERVICE.equals(name); + private void mockVpn(int uid) { + synchronized (mService.mVpns) { + int userId = UserHandle.getUserId(uid); + mMockVpn = new MockVpn(userId); + // This has no effect unless the VPN is actually connected, because things like + // getActiveNetworkForUidInternal call getNetworkAgentInfoForNetId on the VPN + // netId, and check if that network is actually connected. + mService.mVpns.put(userId, mMockVpn); } + } - @Override - protected IpConnectivityMetrics.Logger metricsLogger() { - return mMetricsService; - } + private void setUidRulesChanged(int uidRules) throws RemoteException { + mPolicyListener.onUidRulesChanged(Process.myUid(), uidRules); + } - @Override - protected void registerNetdEventCallback() { - } + private void setRestrictBackgroundChanged(boolean restrictBackground) throws RemoteException { + mPolicyListener.onRestrictBackgroundChanged(restrictBackground); + } - public void mockVpn(int uid) { - synchronized (mVpns) { - int userId = UserHandle.getUserId(uid); - mMockVpn = new MockVpn(userId); - // This has no effect unless the VPN is actually connected, because things like - // getActiveNetworkForUidInternal call getNetworkAgentInfoForNetId on the VPN - // netId, and check if that network is actually connected. - mVpns.put(userId, mMockVpn); - } - } + private Nat464Xlat getNat464Xlat(NetworkAgentWrapper mna) { + return mService.getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd; + } - public void waitForIdle(int timeoutMs) { - waitForIdleHandler(mHandlerThread, timeoutMs); - } + private static class WrappedMultinetworkPolicyTracker extends MultinetworkPolicyTracker { + volatile boolean mConfigRestrictsAvoidBadWifi; + volatile int mConfigMeteredMultipathPreference; - public void waitForIdle() { - waitForIdle(TIMEOUT_MS); + WrappedMultinetworkPolicyTracker(Context c, Handler h, Runnable r) { + super(c, h, r); } - public void setUidRulesChanged(int uidRules) { - try { - mPolicyListener.onUidRulesChanged(Process.myUid(), uidRules); - } catch (RemoteException ignored) { - } + @Override + public boolean configRestrictsAvoidBadWifi() { + return mConfigRestrictsAvoidBadWifi; } - public void setRestrictBackgroundChanged(boolean restrictBackground) { - try { - mPolicyListener.onRestrictBackgroundChanged(restrictBackground); - } catch (RemoteException ignored) { - } + @Override + public int configMeteredMultipathPreference() { + return mConfigMeteredMultipathPreference; } } @@ -1279,6 +1213,8 @@ public class ConnectivityServiceTest { @Before public void setUp() throws Exception { + mNetIdManager = new TestNetIdManager(); + mContext = InstrumentationRegistry.getContext(); MockitoAnnotations.initMocks(this); @@ -1288,6 +1224,13 @@ public class ConnectivityServiceTest { Arrays.asList(new UserInfo[] { new UserInfo(VPN_USER, "", 0), })); + final int userId = UserHandle.getCallingUserId(); + final UserInfo primaryUser = new UserInfo(userId, "", UserInfo.FLAG_PRIMARY); + doReturn(primaryUser).when(mUserManager).getUserInfo(eq(userId)); + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.targetSdkVersion = Build.VERSION_CODES.Q; + when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), any())) + .thenReturn(applicationInfo); // InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not. // http://b/25897652 . @@ -1303,13 +1246,22 @@ public class ConnectivityServiceTest { LocalServices.addService( NetworkPolicyManagerInternal.class, mock(NetworkPolicyManagerInternal.class)); - mService = new WrappedConnectivityService(mServiceContext, + mAlarmManagerThread = new HandlerThread("TestAlarmManager"); + mAlarmManagerThread.start(); + initAlarmManager(mAlarmManager, mAlarmManagerThread.getThreadHandler()); + + mCsHandlerThread = new HandlerThread("TestConnectivityService"); + final ConnectivityService.Dependencies deps = makeDependencies(); + mService = new ConnectivityService(mServiceContext, mNetworkManagementService, mStatsService, mNpm, + mMockDnsResolver, mock(IpConnectivityLog.class), mMockNetd, - mMockDnsResolver); + deps); + mService.mLingerDelayMs = TEST_LINGER_DELAY_MS; + verify(deps).makeMultinetworkPolicyTracker(any(), any(), any()); final ArgumentCaptor<INetworkPolicyListener> policyListenerCaptor = ArgumentCaptor.forClass(INetworkPolicyListener.class); @@ -1320,7 +1272,7 @@ public class ConnectivityServiceTest { // getSystemService() correctly. mCm = new WrappedConnectivityManager(InstrumentationRegistry.getContext(), mService); mService.systemReady(); - mService.mockVpn(Process.myUid()); + mockVpn(Process.myUid()); mCm.bindProcessToNetwork(null); // Ensure that the default setting for Captive Portals is used for most tests @@ -1329,6 +1281,57 @@ public class ConnectivityServiceTest { setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com"); } + private ConnectivityService.Dependencies makeDependencies() { + final MockableSystemProperties systemProperties = spy(new MockableSystemProperties()); + when(systemProperties.getInt("net.tcp.default_init_rwnd", 0)).thenReturn(0); + when(systemProperties.getBoolean("ro.radio.noril", false)).thenReturn(false); + + final ConnectivityService.Dependencies deps = mock(ConnectivityService.Dependencies.class); + doReturn(mCsHandlerThread).when(deps).makeHandlerThread(); + doReturn(mNetIdManager).when(deps).makeNetIdManager(); + doReturn(mNetworkStack).when(deps).getNetworkStack(); + doReturn(systemProperties).when(deps).getSystemProperties(); + doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any()); + doReturn(mMetricsService).when(deps).getMetricsLogger(); + doReturn(true).when(deps).queryUserAccess(anyInt(), anyInt()); + doReturn(mIpConnectivityMetrics).when(deps).getIpConnectivityMetrics(); + doReturn(mBatteryStatsService).when(deps).getBatteryStatsService(); + doReturn(true).when(deps).hasService(Context.ETHERNET_SERVICE); + doAnswer(inv -> { + mPolicyTracker = new WrappedMultinetworkPolicyTracker( + inv.getArgument(0), inv.getArgument(1), inv.getArgument(2)); + return mPolicyTracker; + }).when(deps).makeMultinetworkPolicyTracker(any(), any(), any()); + + return deps; + } + + private static void initAlarmManager(final AlarmManager am, final Handler alarmHandler) { + doAnswer(inv -> { + final long when = inv.getArgument(1); + final WakeupMessage wakeupMsg = inv.getArgument(3); + final Handler handler = inv.getArgument(4); + + long delayMs = when - SystemClock.elapsedRealtime(); + if (delayMs < 0) delayMs = 0; + if (delayMs > UNREASONABLY_LONG_ALARM_WAIT_MS) { + fail("Attempting to send msg more than " + UNREASONABLY_LONG_ALARM_WAIT_MS + + "ms into the future: " + delayMs); + } + alarmHandler.postDelayed(() -> handler.post(wakeupMsg::onAlarm), wakeupMsg /* token */, + delayMs); + + return null; + }).when(am).setExact(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), anyLong(), anyString(), + any(WakeupMessage.class), any()); + + doAnswer(inv -> { + final WakeupMessage wakeupMsg = inv.getArgument(0); + alarmHandler.removeCallbacksAndMessages(wakeupMsg /* token */); + return null; + }).when(am).cancel(any(WakeupMessage.class)); + } + @After public void tearDown() throws Exception { setAlwaysOnNetworks(false); @@ -1344,7 +1347,13 @@ public class ConnectivityServiceTest { mEthernetNetworkAgent.disconnect(); mEthernetNetworkAgent = null; } + mMockVpn.disconnect(); + waitForIdle(); + FakeSettingsProvider.clearSettingsProvider(); + + mCsHandlerThread.quitSafely(); + mAlarmManagerThread.quitSafely(); } private void mockDefaultPackages() throws Exception { @@ -1362,21 +1371,9 @@ public class ConnectivityServiceTest { buildPackageInfo(/* SYSTEM */ false, APP2_UID), buildPackageInfo(/* SYSTEM */ false, VPN_UID) })); - } - - private static int transportToLegacyType(int transport) { - switch (transport) { - case TRANSPORT_ETHERNET: - return TYPE_ETHERNET; - case TRANSPORT_WIFI: - return TYPE_WIFI; - case TRANSPORT_CELLULAR: - return TYPE_MOBILE; - case TRANSPORT_VPN: - return TYPE_VPN; - default: - return TYPE_NONE; - } + final int userId = UserHandle.getCallingUserId(); + when(mPackageManager.getPackageUidAsUser(TEST_PACKAGE_NAME, userId)) + .thenReturn(Process.myUid()); } private void verifyActiveNetwork(int transport) { @@ -1420,21 +1417,79 @@ public class ConnectivityServiceTest { } /** - * Return a ConditionVariable that opens when {@code count} numbers of CONNECTIVITY_ACTION - * broadcasts are received. + * Class to simplify expecting broadcasts using BroadcastInterceptingContext. + * Ensures that the receiver is unregistered after the expected broadcast is received. This + * cannot be done in the BroadcastReceiver itself because BroadcastInterceptingContext runs + * the receivers' receive method while iterating over the list of receivers, and unregistering + * the receiver during iteration throws ConcurrentModificationException. */ - private ConditionVariable waitForConnectivityBroadcasts(final int count) { - final ConditionVariable cv = new ConditionVariable(); - mServiceContext.registerReceiver(new BroadcastReceiver() { - private int remaining = count; - public void onReceive(Context context, Intent intent) { - if (--remaining == 0) { - cv.open(); - mServiceContext.unregisterReceiver(this); - } - } - }, new IntentFilter(CONNECTIVITY_ACTION)); - return cv; + private class ExpectedBroadcast extends CompletableFuture<Intent> { + private final BroadcastReceiver mReceiver; + + ExpectedBroadcast(BroadcastReceiver receiver) { + mReceiver = receiver; + } + + public Intent expectBroadcast(int timeoutMs) throws Exception { + try { + return get(timeoutMs, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + fail("Expected broadcast not received after " + timeoutMs + " ms"); + return null; + } finally { + mServiceContext.unregisterReceiver(mReceiver); + } + } + + public Intent expectBroadcast() throws Exception { + return expectBroadcast(TIMEOUT_MS); + } + + public void expectNoBroadcast(int timeoutMs) throws Exception { + waitForIdle(); + try { + final Intent intent = get(timeoutMs, TimeUnit.MILLISECONDS); + fail("Unexpected broadcast: " + intent.getAction()); + } catch (TimeoutException expected) { + } finally { + mServiceContext.unregisterReceiver(mReceiver); + } + } + } + + /** Expects that {@code count} CONNECTIVITY_ACTION broadcasts are received. */ + private ExpectedBroadcast registerConnectivityBroadcast(final int count) { + return registerConnectivityBroadcastThat(count, intent -> true); + } + + private ExpectedBroadcast registerConnectivityBroadcastThat(final int count, + @NonNull final Predicate<Intent> filter) { + final IntentFilter intentFilter = new IntentFilter(CONNECTIVITY_ACTION); + // AtomicReference allows receiver to access expected even though it is constructed later. + final AtomicReference<ExpectedBroadcast> expectedRef = new AtomicReference<>(); + final BroadcastReceiver receiver = new BroadcastReceiver() { + private int mRemaining = count; + public void onReceive(Context context, Intent intent) { + final int type = intent.getIntExtra(EXTRA_NETWORK_TYPE, -1); + final NetworkInfo ni = intent.getParcelableExtra(EXTRA_NETWORK_INFO); + Log.d(TAG, "Received CONNECTIVITY_ACTION type=" + type + " ni=" + ni); + if (!filter.test(intent)) return; + if (--mRemaining == 0) { + expectedRef.get().complete(intent); + } + } + }; + final ExpectedBroadcast expected = new ExpectedBroadcast(receiver); + expectedRef.set(expected); + mServiceContext.registerReceiver(receiver, intentFilter); + return expected; + } + + private ExpectedBroadcast expectConnectivityAction(int type, NetworkInfo.DetailedState state) { + return registerConnectivityBroadcastThat(1, intent -> + type == intent.getIntExtra(EXTRA_NETWORK_TYPE, -1) && state.equals( + ((NetworkInfo) intent.getParcelableExtra(EXTRA_NETWORK_INFO)) + .getDetailedState())); } @Test @@ -1454,16 +1509,46 @@ public class ConnectivityServiceTest { } @Test + public void testNetworkFeature() throws Exception { + // Connect the cell agent and wait for the connected broadcast. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.addCapability(NET_CAPABILITY_SUPL); + ExpectedBroadcast b = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED); + mCellNetworkAgent.connect(true); + b.expectBroadcast(); + + // Build legacy request for SUPL. + final NetworkCapabilities legacyCaps = new NetworkCapabilities(); + legacyCaps.addTransportType(TRANSPORT_CELLULAR); + legacyCaps.addCapability(NET_CAPABILITY_SUPL); + final NetworkRequest legacyRequest = new NetworkRequest(legacyCaps, TYPE_MOBILE_SUPL, + ConnectivityManager.REQUEST_ID_UNSET, NetworkRequest.Type.REQUEST); + + // File request, withdraw it and make sure no broadcast is sent + b = registerConnectivityBroadcast(1); + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.requestNetwork(legacyRequest, callback); + callback.expectCallback(CallbackEntry.AVAILABLE, mCellNetworkAgent); + mCm.unregisterNetworkCallback(callback); + b.expectNoBroadcast(800); // 800ms long enough to at least flake if this is sent + + // Disconnect the network and expect mobile disconnected broadcast. + b = expectConnectivityAction(TYPE_MOBILE, DetailedState.DISCONNECTED); + mCellNetworkAgent.disconnect(); + b.expectBroadcast(); + } + + @Test public void testLingering() throws Exception { verifyNoNetwork(); - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); assertNull(mCm.getActiveNetworkInfo()); assertNull(mCm.getActiveNetwork()); // Test bringing up validated cellular. - ConditionVariable cv = waitForConnectivityBroadcasts(1); + ExpectedBroadcast b = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED); mCellNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_CELLULAR); assertLength(2, mCm.getAllNetworks()); assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork()) || @@ -1471,9 +1556,9 @@ public class ConnectivityServiceTest { assertTrue(mCm.getAllNetworks()[0].equals(mWiFiNetworkAgent.getNetwork()) || mCm.getAllNetworks()[1].equals(mWiFiNetworkAgent.getNetwork())); // Test bringing up validated WiFi. - cv = waitForConnectivityBroadcasts(2); + b = registerConnectivityBroadcast(2); mWiFiNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); assertLength(2, mCm.getAllNetworks()); assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork()) || @@ -1481,29 +1566,29 @@ public class ConnectivityServiceTest { assertTrue(mCm.getAllNetworks()[0].equals(mCellNetworkAgent.getNetwork()) || mCm.getAllNetworks()[1].equals(mCellNetworkAgent.getNetwork())); // Test cellular linger timeout. - waitFor(mCellNetworkAgent.getDisconnectedCV()); + mCellNetworkAgent.expectDisconnected(); waitForIdle(); assertLength(1, mCm.getAllNetworks()); verifyActiveNetwork(TRANSPORT_WIFI); assertLength(1, mCm.getAllNetworks()); assertEquals(mCm.getAllNetworks()[0], mCm.getActiveNetwork()); // Test WiFi disconnect. - cv = waitForConnectivityBroadcasts(1); + b = registerConnectivityBroadcast(1); mWiFiNetworkAgent.disconnect(); - waitFor(cv); + b.expectBroadcast(); verifyNoNetwork(); } @Test public void testValidatedCellularOutscoresUnvalidatedWiFi() throws Exception { // Test bringing up unvalidated WiFi - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); - ConditionVariable cv = waitForConnectivityBroadcasts(1); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + ExpectedBroadcast b = registerConnectivityBroadcast(1); mWiFiNetworkAgent.connect(false); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); // Test bringing up unvalidated cellular - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(false); waitForIdle(); verifyActiveNetwork(TRANSPORT_WIFI); @@ -1512,71 +1597,71 @@ public class ConnectivityServiceTest { waitForIdle(); verifyActiveNetwork(TRANSPORT_WIFI); // Test bringing up validated cellular - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); - cv = waitForConnectivityBroadcasts(2); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + b = registerConnectivityBroadcast(2); mCellNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test cellular disconnect. - cv = waitForConnectivityBroadcasts(2); + b = registerConnectivityBroadcast(2); mCellNetworkAgent.disconnect(); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); // Test WiFi disconnect. - cv = waitForConnectivityBroadcasts(1); + b = registerConnectivityBroadcast(1); mWiFiNetworkAgent.disconnect(); - waitFor(cv); + b.expectBroadcast(); verifyNoNetwork(); } @Test public void testUnvalidatedWifiOutscoresUnvalidatedCellular() throws Exception { // Test bringing up unvalidated cellular. - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); - ConditionVariable cv = waitForConnectivityBroadcasts(1); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + ExpectedBroadcast b = registerConnectivityBroadcast(1); mCellNetworkAgent.connect(false); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test bringing up unvalidated WiFi. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); - cv = waitForConnectivityBroadcasts(2); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + b = registerConnectivityBroadcast(2); mWiFiNetworkAgent.connect(false); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); // Test WiFi disconnect. - cv = waitForConnectivityBroadcasts(2); + b = registerConnectivityBroadcast(2); mWiFiNetworkAgent.disconnect(); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test cellular disconnect. - cv = waitForConnectivityBroadcasts(1); + b = registerConnectivityBroadcast(1); mCellNetworkAgent.disconnect(); - waitFor(cv); + b.expectBroadcast(); verifyNoNetwork(); } @Test public void testUnlingeringDoesNotValidate() throws Exception { // Test bringing up unvalidated WiFi. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); - ConditionVariable cv = waitForConnectivityBroadcasts(1); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + ExpectedBroadcast b = registerConnectivityBroadcast(1); mWiFiNetworkAgent.connect(false); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); // Test bringing up validated cellular. - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); - cv = waitForConnectivityBroadcasts(2); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + b = registerConnectivityBroadcast(2); mCellNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_CELLULAR); assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); // Test cellular disconnect. - cv = waitForConnectivityBroadcasts(2); + b = registerConnectivityBroadcast(2); mCellNetworkAgent.disconnect(); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); // Unlingering a network should not cause it to be marked as validated. assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( @@ -1586,26 +1671,26 @@ public class ConnectivityServiceTest { @Test public void testCellularOutscoresWeakWifi() throws Exception { // Test bringing up validated cellular. - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); - ConditionVariable cv = waitForConnectivityBroadcasts(1); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + ExpectedBroadcast b = registerConnectivityBroadcast(1); mCellNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test bringing up validated WiFi. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); - cv = waitForConnectivityBroadcasts(2); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + b = registerConnectivityBroadcast(2); mWiFiNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); // Test WiFi getting really weak. - cv = waitForConnectivityBroadcasts(2); + b = registerConnectivityBroadcast(2); mWiFiNetworkAgent.adjustScore(-11); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test WiFi restoring signal strength. - cv = waitForConnectivityBroadcasts(2); + b = registerConnectivityBroadcast(2); mWiFiNetworkAgent.adjustScore(11); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); } @@ -1613,66 +1698,62 @@ public class ConnectivityServiceTest { public void testReapingNetwork() throws Exception { // Test bringing up WiFi without NET_CAPABILITY_INTERNET. // Expect it to be torn down immediately because it satisfies no requests. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); - ConditionVariable cv = mWiFiNetworkAgent.getDisconnectedCV(); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connectWithoutInternet(); - waitFor(cv); + mWiFiNetworkAgent.expectDisconnected(); // Test bringing up cellular without NET_CAPABILITY_INTERNET. // Expect it to be torn down immediately because it satisfies no requests. - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); - cv = mCellNetworkAgent.getDisconnectedCV(); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mCellNetworkAgent.connectWithoutInternet(); - waitFor(cv); + mCellNetworkAgent.expectDisconnected(); // Test bringing up validated WiFi. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); - cv = waitForConnectivityBroadcasts(1); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + final ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); mWiFiNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); // Test bringing up unvalidated cellular. // Expect it to be torn down because it could never be the highest scoring network // satisfying the default request even if it validated. - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); - cv = mCellNetworkAgent.getDisconnectedCV(); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(false); - waitFor(cv); + mCellNetworkAgent.expectDisconnected(); verifyActiveNetwork(TRANSPORT_WIFI); - cv = mWiFiNetworkAgent.getDisconnectedCV(); mWiFiNetworkAgent.disconnect(); - waitFor(cv); + mWiFiNetworkAgent.expectDisconnected(); } @Test public void testCellularFallback() throws Exception { // Test bringing up validated cellular. - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); - ConditionVariable cv = waitForConnectivityBroadcasts(1); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + ExpectedBroadcast b = registerConnectivityBroadcast(1); mCellNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test bringing up validated WiFi. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); - cv = waitForConnectivityBroadcasts(2); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + b = registerConnectivityBroadcast(2); mWiFiNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); // Reevaluate WiFi (it'll instantly fail DNS). - cv = waitForConnectivityBroadcasts(2); + b = registerConnectivityBroadcast(2); assertTrue(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); mCm.reportBadNetwork(mWiFiNetworkAgent.getNetwork()); // Should quickly fall back to Cellular. - waitFor(cv); + b.expectBroadcast(); assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); verifyActiveNetwork(TRANSPORT_CELLULAR); // Reevaluate cellular (it'll instantly fail DNS). - cv = waitForConnectivityBroadcasts(2); + b = registerConnectivityBroadcast(2); assertTrue(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); mCm.reportBadNetwork(mCellNetworkAgent.getNetwork()); // Should quickly fall back to WiFi. - waitFor(cv); + b.expectBroadcast(); assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( @@ -1683,24 +1764,24 @@ public class ConnectivityServiceTest { @Test public void testWiFiFallback() throws Exception { // Test bringing up unvalidated WiFi. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); - ConditionVariable cv = waitForConnectivityBroadcasts(1); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + ExpectedBroadcast b = registerConnectivityBroadcast(1); mWiFiNetworkAgent.connect(false); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); // Test bringing up validated cellular. - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); - cv = waitForConnectivityBroadcasts(2); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + b = registerConnectivityBroadcast(2); mCellNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_CELLULAR); // Reevaluate cellular (it'll instantly fail DNS). - cv = waitForConnectivityBroadcasts(2); + b = registerConnectivityBroadcast(2); assertTrue(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); mCm.reportBadNetwork(mCellNetworkAgent.getNetwork()); // Should quickly fall back to WiFi. - waitFor(cv); + b.expectBroadcast(); assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); verifyActiveNetwork(TRANSPORT_WIFI); @@ -1712,274 +1793,37 @@ public class ConnectivityServiceTest { mCm.getDefaultRequest().networkCapabilities)); } - enum CallbackState { - NONE, - AVAILABLE, - NETWORK_CAPABILITIES, - LINK_PROPERTIES, - SUSPENDED, - RESUMED, - LOSING, - LOST, - UNAVAILABLE, - BLOCKED_STATUS - } - - private static class CallbackInfo { - public final CallbackState state; - public final Network network; - public final Object arg; - public CallbackInfo(CallbackState s, Network n, Object o) { - state = s; network = n; arg = o; - } - public String toString() { - return String.format("%s (%s) (%s)", state, network, arg); - } - @Override - public boolean equals(Object o) { - if (!(o instanceof CallbackInfo)) return false; - // Ignore timeMs, since it's unpredictable. - CallbackInfo other = (CallbackInfo) o; - return (state == other.state) && Objects.equals(network, other.network); - } - @Override - public int hashCode() { - return Objects.hash(state, network); - } - } - /** * Utility NetworkCallback for testing. The caller must explicitly test for all the callbacks * this class receives, by calling expectCallback() exactly once each time a callback is * received. assertNoCallback may be called at any time. */ - private class TestNetworkCallback extends NetworkCallback { - private final LinkedBlockingQueue<CallbackInfo> mCallbacks = new LinkedBlockingQueue<>(); - private Network mLastAvailableNetwork; - - protected void setLastCallback(CallbackState state, Network network, Object o) { - mCallbacks.offer(new CallbackInfo(state, network, o)); - } - - @Override - public void onAvailable(Network network) { - mLastAvailableNetwork = network; - setLastCallback(CallbackState.AVAILABLE, network, null); - } - - @Override - public void onCapabilitiesChanged(Network network, NetworkCapabilities netCap) { - setLastCallback(CallbackState.NETWORK_CAPABILITIES, network, netCap); - } - - @Override - public void onLinkPropertiesChanged(Network network, LinkProperties linkProp) { - setLastCallback(CallbackState.LINK_PROPERTIES, network, linkProp); - } - - @Override - public void onUnavailable() { - setLastCallback(CallbackState.UNAVAILABLE, null, null); - } - - @Override - public void onNetworkSuspended(Network network) { - setLastCallback(CallbackState.SUSPENDED, network, null); - } - - @Override - public void onNetworkResumed(Network network) { - setLastCallback(CallbackState.RESUMED, network, null); - } - - @Override - public void onLosing(Network network, int maxMsToLive) { - setLastCallback(CallbackState.LOSING, network, maxMsToLive /* autoboxed int */); + private class TestNetworkCallback extends TestableNetworkCallback { + TestNetworkCallback() { + super(TEST_CALLBACK_TIMEOUT_MS); } @Override - public void onLost(Network network) { - mLastAvailableNetwork = null; - setLastCallback(CallbackState.LOST, network, null); + public void assertNoCallback() { + // TODO: better support this use case in TestableNetworkCallback + waitForIdle(); + assertNoCallback(0 /* timeout */); } @Override - public void onBlockedStatusChanged(Network network, boolean blocked) { - setLastCallback(CallbackState.BLOCKED_STATUS, network, blocked); - } - - public Network getLastAvailableNetwork() { - return mLastAvailableNetwork; - } - - CallbackInfo nextCallback(int timeoutMs) { - CallbackInfo cb = null; - try { - cb = mCallbacks.poll(timeoutMs, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - } - if (cb == null) { - // LinkedBlockingQueue.poll() returns null if it timeouts. - fail("Did not receive callback after " + timeoutMs + "ms"); - } - return cb; - } - - CallbackInfo expectCallback(CallbackState state, MockNetworkAgent agent, int timeoutMs) { - final Network expectedNetwork = (agent != null) ? agent.getNetwork() : null; - CallbackInfo expected = new CallbackInfo(state, expectedNetwork, 0); - CallbackInfo actual = nextCallback(timeoutMs); - assertEquals("Unexpected callback:", expected, actual); - - if (state == CallbackState.LOSING) { + public <T extends CallbackEntry> T expectCallback(final KClass<T> type, final HasNetwork n, + final long timeoutMs) { + final T callback = super.expectCallback(type, n, timeoutMs); + if (callback instanceof CallbackEntry.Losing) { + // TODO : move this to the specific test(s) needing this rather than here. + final CallbackEntry.Losing losing = (CallbackEntry.Losing) callback; + final int maxMsToLive = losing.getMaxMsToLive(); String msg = String.format( "Invalid linger time value %d, must be between %d and %d", - actual.arg, 0, mService.mLingerDelayMs); - int maxMsToLive = (Integer) actual.arg; + maxMsToLive, 0, mService.mLingerDelayMs); assertTrue(msg, 0 <= maxMsToLive && maxMsToLive <= mService.mLingerDelayMs); } - - return actual; - } - - CallbackInfo expectCallback(CallbackState state, MockNetworkAgent agent) { - return expectCallback(state, agent, TEST_CALLBACK_TIMEOUT_MS); - } - - CallbackInfo expectCallbackLike(Predicate<CallbackInfo> fn) { - return expectCallbackLike(fn, TEST_CALLBACK_TIMEOUT_MS); - } - - CallbackInfo expectCallbackLike(Predicate<CallbackInfo> fn, int timeoutMs) { - int timeLeft = timeoutMs; - while (timeLeft > 0) { - long start = SystemClock.elapsedRealtime(); - CallbackInfo info = nextCallback(timeLeft); - if (fn.test(info)) { - return info; - } - timeLeft -= (SystemClock.elapsedRealtime() - start); - } - fail("Did not receive expected callback after " + timeoutMs + "ms"); - return null; - } - - // Expects onAvailable and the callbacks that follow it. These are: - // - onSuspended, iff the network was suspended when the callbacks fire. - // - onCapabilitiesChanged. - // - onLinkPropertiesChanged. - // - onBlockedStatusChanged. - // - // @param agent the network to expect the callbacks on. - // @param expectSuspended whether to expect a SUSPENDED callback. - // @param expectValidated the expected value of the VALIDATED capability in the - // onCapabilitiesChanged callback. - // @param timeoutMs how long to wait for the callbacks. - void expectAvailableCallbacks(MockNetworkAgent agent, boolean expectSuspended, - boolean expectValidated, boolean expectBlocked, int timeoutMs) { - expectCallback(CallbackState.AVAILABLE, agent, timeoutMs); - if (expectSuspended) { - expectCallback(CallbackState.SUSPENDED, agent, timeoutMs); - } - if (expectValidated) { - expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent, timeoutMs); - } else { - expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, agent, timeoutMs); - } - expectCallback(CallbackState.LINK_PROPERTIES, agent, timeoutMs); - expectBlockedStatusCallback(expectBlocked, agent); - } - - // Expects the available callbacks (validated), plus onSuspended. - void expectAvailableAndSuspendedCallbacks(MockNetworkAgent agent, boolean expectValidated) { - expectAvailableCallbacks(agent, true, expectValidated, false, TEST_CALLBACK_TIMEOUT_MS); - } - - void expectAvailableCallbacksValidated(MockNetworkAgent agent) { - expectAvailableCallbacks(agent, false, true, false, TEST_CALLBACK_TIMEOUT_MS); - } - - void expectAvailableCallbacksValidatedAndBlocked(MockNetworkAgent agent) { - expectAvailableCallbacks(agent, false, true, true, TEST_CALLBACK_TIMEOUT_MS); - } - - void expectAvailableCallbacksUnvalidated(MockNetworkAgent agent) { - expectAvailableCallbacks(agent, false, false, false, TEST_CALLBACK_TIMEOUT_MS); - } - - void expectAvailableCallbacksUnvalidatedAndBlocked(MockNetworkAgent agent) { - expectAvailableCallbacks(agent, false, false, true, TEST_CALLBACK_TIMEOUT_MS); - } - - // Expects the available callbacks (where the onCapabilitiesChanged must contain the - // VALIDATED capability), plus another onCapabilitiesChanged which is identical to the - // one we just sent. - // TODO: this is likely a bug. Fix it and remove this method. - void expectAvailableDoubleValidatedCallbacks(MockNetworkAgent agent) { - expectCallback(CallbackState.AVAILABLE, agent, TEST_CALLBACK_TIMEOUT_MS); - NetworkCapabilities nc1 = expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent); - expectCallback(CallbackState.LINK_PROPERTIES, agent, TEST_CALLBACK_TIMEOUT_MS); - // Implicitly check the network is allowed to use. - // TODO: should we need to consider if network is in blocked status in this case? - expectBlockedStatusCallback(false, agent); - NetworkCapabilities nc2 = expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent); - assertEquals(nc1, nc2); - } - - // Expects the available callbacks where the onCapabilitiesChanged must not have validated, - // then expects another onCapabilitiesChanged that has the validated bit set. This is used - // when a network connects and satisfies a callback, and then immediately validates. - void expectAvailableThenValidatedCallbacks(MockNetworkAgent agent) { - expectAvailableCallbacksUnvalidated(agent); - expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent); - } - - NetworkCapabilities expectCapabilitiesWith(int capability, MockNetworkAgent agent) { - return expectCapabilitiesWith(capability, agent, TEST_CALLBACK_TIMEOUT_MS); - } - - NetworkCapabilities expectCapabilitiesWith(int capability, MockNetworkAgent agent, - int timeoutMs) { - CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent, timeoutMs); - NetworkCapabilities nc = (NetworkCapabilities) cbi.arg; - assertTrue(nc.hasCapability(capability)); - return nc; - } - - NetworkCapabilities expectCapabilitiesWithout(int capability, MockNetworkAgent agent) { - return expectCapabilitiesWithout(capability, agent, TEST_CALLBACK_TIMEOUT_MS); - } - - NetworkCapabilities expectCapabilitiesWithout(int capability, MockNetworkAgent agent, - int timeoutMs) { - CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent, timeoutMs); - NetworkCapabilities nc = (NetworkCapabilities) cbi.arg; - assertFalse(nc.hasCapability(capability)); - return nc; - } - - void expectCapabilitiesLike(Predicate<NetworkCapabilities> fn, MockNetworkAgent agent) { - CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent); - assertTrue("Received capabilities don't match expectations : " + cbi.arg, - fn.test((NetworkCapabilities) cbi.arg)); - } - - void expectLinkPropertiesLike(Predicate<LinkProperties> fn, MockNetworkAgent agent) { - CallbackInfo cbi = expectCallback(CallbackState.LINK_PROPERTIES, agent); - assertTrue("Received LinkProperties don't match expectations : " + cbi.arg, - fn.test((LinkProperties) cbi.arg)); - } - - void expectBlockedStatusCallback(boolean expectBlocked, MockNetworkAgent agent) { - CallbackInfo cbi = expectCallback(CallbackState.BLOCKED_STATUS, agent); - boolean actualBlocked = (boolean) cbi.arg; - assertEquals(expectBlocked, actualBlocked); - } - - void assertNoCallback() { - waitForIdle(); - CallbackInfo c = mCallbacks.peek(); - assertNull("Unexpected callback: " + c, c); + return callback; } } @@ -2007,13 +1851,13 @@ public class ConnectivityServiceTest { mCm.registerNetworkCallback(cellRequest, cellNetworkCallback); // Test unvalidated networks - ConditionVariable cv = waitForConnectivityBroadcasts(1); - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + ExpectedBroadcast b = registerConnectivityBroadcast(1); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(false); genericNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); cellNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); - waitFor(cv); + b.expectBroadcast(); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); // This should not trigger spurious onAvailable() callbacks, b/21762680. @@ -2022,32 +1866,32 @@ public class ConnectivityServiceTest { assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); - cv = waitForConnectivityBroadcasts(2); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + b = registerConnectivityBroadcast(2); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); - waitFor(cv); + b.expectBroadcast(); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); - cv = waitForConnectivityBroadcasts(2); + b = registerConnectivityBroadcast(2); mWiFiNetworkAgent.disconnect(); - genericNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - wifiNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + genericNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + wifiNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); cellNetworkCallback.assertNoCallback(); - waitFor(cv); + b.expectBroadcast(); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); - cv = waitForConnectivityBroadcasts(1); + b = registerConnectivityBroadcast(1); mCellNetworkAgent.disconnect(); - genericNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); - cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); - waitFor(cv); + genericNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + b.expectBroadcast(); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); // Test validated networks - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); genericNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); @@ -2060,29 +1904,92 @@ public class ConnectivityServiceTest { assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); - genericNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + genericNetworkCallback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); genericNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); wifiNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); - cellNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); mWiFiNetworkAgent.disconnect(); - genericNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - wifiNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + genericNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + wifiNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); mCellNetworkAgent.disconnect(); - genericNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); - cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); + genericNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); } + private void doNetworkCallbacksSanitizationTest(boolean sanitized) throws Exception { + final TestNetworkCallback callback = new TestNetworkCallback(); + final TestNetworkCallback defaultCallback = new TestNetworkCallback(); + final NetworkRequest wifiRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_WIFI).build(); + mCm.registerNetworkCallback(wifiRequest, callback); + mCm.registerDefaultNetworkCallback(defaultCallback); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + + final LinkProperties newLp = new LinkProperties(); + final Uri capportUrl = Uri.parse("https://capport.example.com/api"); + final CaptivePortalData capportData = new CaptivePortalData.Builder() + .setCaptive(true).build(); + + final Uri expectedCapportUrl = sanitized ? null : capportUrl; + newLp.setCaptivePortalApiUrl(capportUrl); + mWiFiNetworkAgent.sendLinkProperties(newLp); + callback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp -> + Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl())); + defaultCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp -> + Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl())); + + final CaptivePortalData expectedCapportData = sanitized ? null : capportData; + mWiFiNetworkAgent.notifyCaptivePortalDataChanged(capportData); + callback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp -> + Objects.equals(expectedCapportData, lp.getCaptivePortalData())); + defaultCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp -> + Objects.equals(expectedCapportData, lp.getCaptivePortalData())); + + final LinkProperties lp = mCm.getLinkProperties(mWiFiNetworkAgent.getNetwork()); + assertEquals(expectedCapportUrl, lp.getCaptivePortalApiUrl()); + assertEquals(expectedCapportData, lp.getCaptivePortalData()); + } + + @Test + public void networkCallbacksSanitizationTest_Sanitize() throws Exception { + mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + PERMISSION_DENIED); + mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, + PERMISSION_DENIED); + doNetworkCallbacksSanitizationTest(true /* sanitized */); + } + @Test - public void testMultipleLingering() { + public void networkCallbacksSanitizationTest_NoSanitize_NetworkStack() throws Exception { + mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + PERMISSION_GRANTED); + mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, PERMISSION_DENIED); + doNetworkCallbacksSanitizationTest(false /* sanitized */); + } + + @Test + public void networkCallbacksSanitizationTest_NoSanitize_Settings() throws Exception { + mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + PERMISSION_DENIED); + mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED); + doNetworkCallbacksSanitizationTest(false /* sanitized */); + } + + @Test + public void testMultipleLingering() throws Exception { // This test would be flaky with the default 120ms timer: that is short enough that // lingered networks are torn down before assertions can be run. We don't want to mock the // lingering timer to keep the WakeupMessage logic realistic: this has already proven useful @@ -2098,9 +2005,9 @@ public class ConnectivityServiceTest { TestNetworkCallback defaultCallback = new TestNetworkCallback(); mCm.registerDefaultNetworkCallback(defaultCallback); - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); - mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET); mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); @@ -2117,7 +2024,7 @@ public class ConnectivityServiceTest { // We then get LOSING when wifi validates and cell is outscored. callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); // TODO: Investigate sending validated before losing. - callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); @@ -2126,20 +2033,20 @@ public class ConnectivityServiceTest { mEthernetNetworkAgent.connect(true); callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent); // TODO: Investigate sending validated before losing. - callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent); + callback.expectCallback(CallbackEntry.LOSING, mWiFiNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent); defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent); assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); mEthernetNetworkAgent.disconnect(); - callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent); - defaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent); + callback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); for (int i = 0; i < 4; i++) { - MockNetworkAgent oldNetwork, newNetwork; + TestNetworkAgentWrapper oldNetwork, newNetwork; if (i % 2 == 0) { mWiFiNetworkAgent.adjustScore(-15); oldNetwork = mWiFiNetworkAgent; @@ -2150,7 +2057,7 @@ public class ConnectivityServiceTest { newNetwork = mWiFiNetworkAgent; } - callback.expectCallback(CallbackState.LOSING, oldNetwork); + callback.expectCallback(CallbackEntry.LOSING, oldNetwork); // TODO: should we send an AVAILABLE callback to newNetwork, to indicate that it is no // longer lingering? defaultCallback.expectAvailableCallbacksValidated(newNetwork); @@ -2164,7 +2071,7 @@ public class ConnectivityServiceTest { // We expect a notification about the capabilities change, and nothing else. defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, mWiFiNetworkAgent); defaultCallback.assertNoCallback(); - callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); // Wifi no longer satisfies our listen, which is for an unmetered network. @@ -2173,11 +2080,11 @@ public class ConnectivityServiceTest { // Disconnect our test networks. mWiFiNetworkAgent.disconnect(); - defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); mCellNetworkAgent.disconnect(); - defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); waitForIdle(); assertEquals(null, mCm.getActiveNetwork()); @@ -2191,7 +2098,7 @@ public class ConnectivityServiceTest { mCm.registerNetworkCallback(request, callback); - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(false); // Score: 10 callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); @@ -2200,7 +2107,7 @@ public class ConnectivityServiceTest { // Bring up wifi with a score of 20. // Cell stays up because it would satisfy the default request if it validated. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); // Score: 20 callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); @@ -2208,65 +2115,65 @@ public class ConnectivityServiceTest { assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); mWiFiNetworkAgent.disconnect(); - callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); // Bring up wifi with a score of 70. // Cell is lingered because it would not satisfy any request, even if it validated. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.adjustScore(50); mWiFiNetworkAgent.connect(false); // Score: 70 callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); - callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); // Tear down wifi. mWiFiNetworkAgent.disconnect(); - callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); // Bring up wifi, then validate it. Previous versions would immediately tear down cell, but // it's arguably correct to linger it, since it was the default network before it validated. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); // TODO: Investigate sending validated before losing. - callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); mWiFiNetworkAgent.disconnect(); - callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); mCellNetworkAgent.disconnect(); - callback.expectCallback(CallbackState.LOST, mCellNetworkAgent); - defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); + callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); waitForIdle(); assertEquals(null, mCm.getActiveNetwork()); // If a network is lingering, and we add and remove a request from it, resume lingering. - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); // TODO: Investigate sending validated before losing. - callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); @@ -2277,13 +2184,13 @@ public class ConnectivityServiceTest { // TODO: should this cause an AVAILABLE callback, to indicate that the network is no longer // lingering? mCm.unregisterNetworkCallback(noopCallback); - callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); // Similar to the above: lingering can start even after the lingered request is removed. // Disconnect wifi and switch to cell. mWiFiNetworkAgent.disconnect(); - callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); @@ -2292,7 +2199,7 @@ public class ConnectivityServiceTest { mCm.requestNetwork(cellRequest, noopCallback); // Now connect wifi, and expect it to become the default network. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); callback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); @@ -2302,34 +2209,34 @@ public class ConnectivityServiceTest { callback.assertNoCallback(); // Now unregister cellRequest and expect cell to start lingering. mCm.unregisterNetworkCallback(noopCallback); - callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); // Let linger run its course. callback.assertNoCallback(); final int lingerTimeoutMs = mService.mLingerDelayMs + mService.mLingerDelayMs / 4; - callback.expectCallback(CallbackState.LOST, mCellNetworkAgent, lingerTimeoutMs); + callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent, lingerTimeoutMs); // Register a TRACK_DEFAULT request and check that it does not affect lingering. TestNetworkCallback trackDefaultCallback = new TestNetworkCallback(); mCm.registerDefaultNetworkCallback(trackDefaultCallback); trackDefaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); - mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET); + mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET); mEthernetNetworkAgent.connect(true); callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent); - callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent); + callback.expectCallback(CallbackEntry.LOSING, mWiFiNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent); trackDefaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent); defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); // Let linger run its course. - callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent, lingerTimeoutMs); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent, lingerTimeoutMs); // Clean up. mEthernetNetworkAgent.disconnect(); - callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent); - defaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent); - trackDefaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent); + callback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); + trackDefaultCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); mCm.unregisterNetworkCallback(callback); mCm.unregisterNetworkCallback(defaultCallback); @@ -2356,8 +2263,8 @@ public class ConnectivityServiceTest { TestNetworkCallback defaultCallback = new TestNetworkCallback(); mCm.registerDefaultNetworkCallback(defaultCallback); - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mCellNetworkAgent.connect(true); callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); @@ -2367,7 +2274,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.connect(true); defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); - callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); // File a request for cellular, then release it. @@ -2376,7 +2283,7 @@ public class ConnectivityServiceTest { NetworkCallback noopCallback = new NetworkCallback(); mCm.requestNetwork(cellRequest, noopCallback); mCm.unregisterNetworkCallback(noopCallback); - callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); // Let linger run its course. callback.assertNoCallback(); @@ -2390,7 +2297,7 @@ public class ConnectivityServiceTest { } @Test - public void testExplicitlySelected() { + public void testExplicitlySelected() throws Exception { NetworkRequest request = new NetworkRequest.Builder() .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET) .build(); @@ -2398,13 +2305,13 @@ public class ConnectivityServiceTest { mCm.registerNetworkCallback(request, callback); // Bring up validated cell. - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); // Bring up unvalidated wifi with explicitlySelected=true. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); - mWiFiNetworkAgent.explicitlySelected(false); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.explicitlySelected(true, false); mWiFiNetworkAgent.connect(false); callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); @@ -2420,47 +2327,69 @@ public class ConnectivityServiceTest { // If the user chooses yes on the "No Internet access, stay connected?" dialog, we switch to // wifi even though it's unvalidated. mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), true, false); - callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); // Disconnect wifi, and then reconnect, again with explicitlySelected=true. mWiFiNetworkAgent.disconnect(); - callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); - mWiFiNetworkAgent.explicitlySelected(false); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.explicitlySelected(true, false); mWiFiNetworkAgent.connect(false); callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); // If the user chooses no on the "No Internet access, stay connected?" dialog, we ask the // network to disconnect. mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), false, false); - callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); // Reconnect, again with explicitlySelected=true, but this time validate. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); - mWiFiNetworkAgent.explicitlySelected(false); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.explicitlySelected(true, false); mWiFiNetworkAgent.connect(true); callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); - callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); // BUG: the network will no longer linger, even though it's validated and outscored. // TODO: fix this. - mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET); + mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET); mEthernetNetworkAgent.connect(true); callback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent); assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork()); callback.assertNoCallback(); + // Disconnect wifi, and then reconnect as if the user had selected "yes, don't ask again" + // (i.e., with explicitlySelected=true and acceptUnvalidated=true). Expect to switch to + // wifi immediately. + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.explicitlySelected(true, true); + mWiFiNetworkAgent.connect(false); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + callback.expectCallback(CallbackEntry.LOSING, mEthernetNetworkAgent); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + mEthernetNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); + + // Disconnect and reconnect with explicitlySelected=false and acceptUnvalidated=true. + // Check that the network is not scored specially and that the device prefers cell data. + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.explicitlySelected(false, true); + mWiFiNetworkAgent.connect(false); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + // Clean up. mWiFiNetworkAgent.disconnect(); mCellNetworkAgent.disconnect(); - mEthernetNetworkAgent.disconnect(); - callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - callback.expectCallback(CallbackState.LOST, mCellNetworkAgent); - callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); } private int[] makeIntArray(final int size, final int value) { @@ -2511,7 +2440,7 @@ public class ConnectivityServiceTest { assertTrue(testFactory.getMyStartRequested()); // Now bring in a higher scored network. - MockNetworkAgent testAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + TestNetworkAgentWrapper testAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); // Rather than create a validated network which complicates things by registering it's // own NetworkRequest during startup, just bump up the score to cancel out the // unvalidated penalty. @@ -2560,7 +2489,7 @@ public class ConnectivityServiceTest { assertEquals(expectedRequestCount, testFactory.getMyRequestCount()); assertTrue(testFactory.getMyStartRequested()); - testFactory.unregister(); + testFactory.terminate(); if (networkCallback != null) mCm.unregisterNetworkCallback(networkCallback); handlerThread.quit(); } @@ -2586,6 +2515,38 @@ public class ConnectivityServiceTest { } @Test + public void testNetworkFactoryUnregister() throws Exception { + final NetworkCapabilities filter = new NetworkCapabilities(); + filter.clearAll(); + + final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests"); + handlerThread.start(); + + // Checks that calling setScoreFilter on a NetworkFactory immediately before closing it + // does not crash. + for (int i = 0; i < 100; i++) { + final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), + mServiceContext, "testFactory", filter); + // Register the factory and don't be surprised when the default request arrives. + testFactory.expectAddRequestsWithScores(0); + testFactory.register(); + testFactory.waitForNetworkRequests(1); + + testFactory.setScoreFilter(42); + testFactory.terminate(); + + if (i % 2 == 0) { + try { + testFactory.register(); + fail("Re-registering terminated NetworkFactory should throw"); + } catch (IllegalStateException expected) { + } + } + } + handlerThread.quit(); + } + + @Test public void testNoMutableNetworkRequests() throws Exception { PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, new Intent("a"), 0); NetworkRequest request1 = new NetworkRequest.Builder() @@ -2596,29 +2557,28 @@ public class ConnectivityServiceTest { .build(); Class<IllegalArgumentException> expected = IllegalArgumentException.class; - assertException(() -> { mCm.requestNetwork(request1, new NetworkCallback()); }, expected); - assertException(() -> { mCm.requestNetwork(request1, pendingIntent); }, expected); - assertException(() -> { mCm.requestNetwork(request2, new NetworkCallback()); }, expected); - assertException(() -> { mCm.requestNetwork(request2, pendingIntent); }, expected); + assertThrows(expected, () -> mCm.requestNetwork(request1, new NetworkCallback())); + assertThrows(expected, () -> mCm.requestNetwork(request1, pendingIntent)); + assertThrows(expected, () -> mCm.requestNetwork(request2, new NetworkCallback())); + assertThrows(expected, () -> mCm.requestNetwork(request2, pendingIntent)); } @Test public void testMMSonWiFi() throws Exception { // Test bringing up cellular without MMS NetworkRequest gets reaped - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS); - ConditionVariable cv = mCellNetworkAgent.getDisconnectedCV(); mCellNetworkAgent.connectWithoutInternet(); - waitFor(cv); + mCellNetworkAgent.expectDisconnected(); waitForIdle(); assertEmpty(mCm.getAllNetworks()); verifyNoNetwork(); // Test bringing up validated WiFi. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); - cv = waitForConnectivityBroadcasts(1); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + final ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); mWiFiNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); // Register MMS NetworkRequest @@ -2628,26 +2588,25 @@ public class ConnectivityServiceTest { mCm.requestNetwork(builder.build(), networkCallback); // Test bringing up unvalidated cellular with MMS - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS); mCellNetworkAgent.connectWithoutInternet(); networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); verifyActiveNetwork(TRANSPORT_WIFI); // Test releasing NetworkRequest disconnects cellular with MMS - cv = mCellNetworkAgent.getDisconnectedCV(); mCm.unregisterNetworkCallback(networkCallback); - waitFor(cv); + mCellNetworkAgent.expectDisconnected(); verifyActiveNetwork(TRANSPORT_WIFI); } @Test public void testMMSonCell() throws Exception { // Test bringing up cellular without MMS - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); - ConditionVariable cv = waitForConnectivityBroadcasts(1); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + ExpectedBroadcast b = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED); mCellNetworkAgent.connect(false); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_CELLULAR); // Register MMS NetworkRequest @@ -2657,21 +2616,21 @@ public class ConnectivityServiceTest { mCm.requestNetwork(builder.build(), networkCallback); // Test bringing up MMS cellular network - MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + TestNetworkAgentWrapper + mmsNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS); mmsNetworkAgent.connectWithoutInternet(); networkCallback.expectAvailableCallbacksUnvalidated(mmsNetworkAgent); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent - cv = mmsNetworkAgent.getDisconnectedCV(); mCm.unregisterNetworkCallback(networkCallback); - waitFor(cv); + mmsNetworkAgent.expectDisconnected(); verifyActiveNetwork(TRANSPORT_CELLULAR); } @Test - public void testPartialConnectivity() { + public void testPartialConnectivity() throws Exception { // Register network callback. NetworkRequest request = new NetworkRequest.Builder() .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET) @@ -2680,12 +2639,12 @@ public class ConnectivityServiceTest { mCm.registerNetworkCallback(request, callback); // Bring up validated mobile data. - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); // Bring up wifi with partial connectivity. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connectWithPartialConnectivity(); callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent); @@ -2696,7 +2655,7 @@ public class ConnectivityServiceTest { // With HTTPS probe disabled, NetworkMonitor should pass the network validation with http // probe. - mWiFiNetworkAgent.setNetworkPartialValid(); + mWiFiNetworkAgent.setNetworkPartialValid(false /* isStrictMode */); // If the user chooses yes to use this partial connectivity wifi, switch the default // network to wifi and check if wifi becomes valid or not. mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */, @@ -2704,15 +2663,12 @@ public class ConnectivityServiceTest { // If user accepts partial connectivity network, // NetworkMonitor#setAcceptPartialConnectivity() should be called too. waitForIdle(); - try { - verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity(); - } catch (RemoteException e) { - fail(e.getMessage()); - } + verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity(); + // Need a trigger point to let NetworkMonitor tell ConnectivityService that network is // validated. mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true); - callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); NetworkCapabilities nc = callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); assertTrue(nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)); @@ -2720,8 +2676,8 @@ public class ConnectivityServiceTest { // Disconnect and reconnect wifi with partial connectivity again. mWiFiNetworkAgent.disconnect(); - callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connectWithPartialConnectivity(); callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent); @@ -2732,69 +2688,76 @@ public class ConnectivityServiceTest { // If the user chooses no, disconnect wifi immediately. mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), false/* accept */, false /* always */); - callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); // If user accepted partial connectivity before, and device reconnects to that network // again, but now the network has full connectivity. The network shouldn't contain // NET_CAPABILITY_PARTIAL_CONNECTIVITY. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); // acceptUnvalidated is also used as setting for accepting partial networks. - mWiFiNetworkAgent.explicitlySelected(true /* acceptUnvalidated */); + mWiFiNetworkAgent.explicitlySelected(true /* explicitlySelected */, + true /* acceptUnvalidated */); mWiFiNetworkAgent.connect(true); + // If user accepted partial connectivity network before, // NetworkMonitor#setAcceptPartialConnectivity() will be called in // ConnectivityService#updateNetworkInfo(). - waitForIdle(); - try { - verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity(); - } catch (RemoteException e) { - fail(e.getMessage()); - } callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); - callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity(); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); nc = callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); assertFalse(nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)); + // Wifi should be the default network. assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); mWiFiNetworkAgent.disconnect(); - callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - - // If user accepted partial connectivity before, and now the device reconnects to the - // partial connectivity network. The network should be valid and contain - // NET_CAPABILITY_PARTIAL_CONNECTIVITY. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); - mWiFiNetworkAgent.explicitlySelected(true /* acceptUnvalidated */); - // Current design cannot send multi-testResult from NetworkMonitor to ConnectivityService. - // So, if user accepts partial connectivity, NetworkMonitor will send PARTIAL_CONNECTIVITY - // to ConnectivityService first then send VALID. Once NetworkMonitor support - // multi-testResult, this test case also need to be changed to meet the new design. + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + + // The user accepted partial connectivity and selected "don't ask again". Now the user + // reconnects to the partial connectivity network. Switch to wifi as soon as partial + // connectivity is detected. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.explicitlySelected(true /* explicitlySelected */, + true /* acceptUnvalidated */); mWiFiNetworkAgent.connectWithPartialConnectivity(); // If user accepted partial connectivity network before, // NetworkMonitor#setAcceptPartialConnectivity() will be called in // ConnectivityService#updateNetworkInfo(). - waitForIdle(); - try { - verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity(); - } catch (RemoteException e) { - fail(e.getMessage()); - } callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); - callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); - // TODO: If the user accepted partial connectivity, we shouldn't switch to wifi until - // NetworkMonitor detects partial connectivity + verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity(); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent); - mWiFiNetworkAgent.setNetworkValid(); + mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */); + // Need a trigger point to let NetworkMonitor tell ConnectivityService that network is // validated. mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); mWiFiNetworkAgent.disconnect(); - callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + + // If the user accepted partial connectivity, and the device auto-reconnects to the partial + // connectivity network, it should contain both PARTIAL_CONNECTIVITY and VALIDATED. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.explicitlySelected(false /* explicitlySelected */, + true /* acceptUnvalidated */); + + // NetworkMonitor will immediately (once the HTTPS probe fails...) report the network as + // valid, because ConnectivityService calls setAcceptPartialConnectivity before it calls + // notifyNetworkConnected. + mWiFiNetworkAgent.connectWithPartialValidConnectivity(false /* isStrictMode */); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity(); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); + callback.expectCapabilitiesWith( + NET_CAPABILITY_PARTIAL_CONNECTIVITY | NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); } @Test - public void testCaptivePortalOnPartialConnectivity() throws RemoteException { + public void testCaptivePortalOnPartialConnectivity() throws Exception { final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build(); @@ -2807,9 +2770,9 @@ public class ConnectivityServiceTest { // Bring up a network with a captive portal. // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); String redirectUrl = "http://android.com/path"; - mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl); + mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl, false /* isStrictMode */); captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), redirectUrl); @@ -2827,12 +2790,12 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent); // Report partial connectivity is accepted. - mWiFiNetworkAgent.setNetworkPartialValid(); + mWiFiNetworkAgent.setNetworkPartialValid(false /* isStrictMode */); mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */, false /* always */); waitForIdle(); mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true); - captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + captivePortalCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); NetworkCapabilities nc = validatedCallback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, @@ -2843,7 +2806,7 @@ public class ConnectivityServiceTest { } @Test - public void testCaptivePortal() { + public void testCaptivePortal() throws Exception { final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build(); @@ -2856,46 +2819,43 @@ public class ConnectivityServiceTest { // Bring up a network with a captive portal. // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); String firstRedirectUrl = "http://example.com/firstPath"; - mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl); + mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl, false /* isStrictMode */); captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), firstRedirectUrl); // Take down network. // Expect onLost callback. mWiFiNetworkAgent.disconnect(); - captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + captivePortalCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); // Bring up a network with a captive portal. // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); String secondRedirectUrl = "http://example.com/secondPath"; - mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl); + mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl, false /* isStrictMode */); captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), secondRedirectUrl); // Make captive portal disappear then revalidate. // Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL. - mWiFiNetworkAgent.setNetworkValid(); + mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */); mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true); - captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + captivePortalCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); // Expect NET_CAPABILITY_VALIDATED onAvailable callback. validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); - // Expect no notification to be shown when captive portal disappears by itself - verify(mNotificationManager, never()).notifyAsUser( - anyString(), eq(NotificationType.LOGGED_IN.eventId), any(), any()); // Break network connectivity. // Expect NET_CAPABILITY_VALIDATED onLost callback. - mWiFiNetworkAgent.setNetworkInvalid(); + mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */); mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false); - validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + validatedCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); } @Test - public void testCaptivePortalApp() throws RemoteException { + public void testCaptivePortalApp() throws Exception { final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build(); @@ -2907,7 +2867,7 @@ public class ConnectivityServiceTest { mCm.registerNetworkCallback(validatedRequest, validatedCallback); // Bring up wifi. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); Network wifiNetwork = mWiFiNetworkAgent.getNetwork(); @@ -2920,10 +2880,10 @@ public class ConnectivityServiceTest { mServiceContext.expectNoStartActivityIntent(fastTimeoutMs); // Turn into a captive portal. - mWiFiNetworkAgent.setNetworkPortal("http://example.com"); + mWiFiNetworkAgent.setNetworkPortal("http://example.com", false /* isStrictMode */); mCm.reportNetworkConnectivity(wifiNetwork, false); captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); - validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + validatedCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); // Check that startCaptivePortalApp sends the expected command to NetworkMonitor. mCm.startCaptivePortalApp(wifiNetwork); @@ -2935,25 +2895,25 @@ public class ConnectivityServiceTest { final String testKey = "testkey"; final String testValue = "testvalue"; testBundle.putString(testKey, testValue); + mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + PERMISSION_GRANTED); mCm.startCaptivePortalApp(wifiNetwork, testBundle); final Intent signInIntent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS); assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, signInIntent.getAction()); assertEquals(testValue, signInIntent.getStringExtra(testKey)); // Report that the captive portal is dismissed, and check that callbacks are fired - mWiFiNetworkAgent.setNetworkValid(); + mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */); mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid()); validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); - captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - verify(mNotificationManager, times(1)).notifyAsUser(anyString(), - eq(NotificationType.LOGGED_IN.eventId), any(), eq(UserHandle.ALL)); + captivePortalCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); mCm.unregisterNetworkCallback(validatedCallback); mCm.unregisterNetworkCallback(captivePortalCallback); } @Test - public void testAvoidOrIgnoreCaptivePortals() { + public void testAvoidOrIgnoreCaptivePortals() throws Exception { final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build(); @@ -2967,18 +2927,50 @@ public class ConnectivityServiceTest { setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_AVOID); // Bring up a network with a captive portal. // Expect it to fail to connect and not result in any callbacks. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); String firstRedirectUrl = "http://example.com/firstPath"; - ConditionVariable disconnectCv = mWiFiNetworkAgent.getDisconnectedCV(); - ConditionVariable avoidCv = mWiFiNetworkAgent.getPreventReconnectReceived(); - mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl); - waitFor(disconnectCv); - waitFor(avoidCv); + mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl, false /* isStrictMode */); + mWiFiNetworkAgent.expectDisconnected(); + mWiFiNetworkAgent.expectPreventReconnectReceived(); assertNoCallbacks(captivePortalCallback, validatedCallback); } + @Test + public void testCaptivePortalApi() throws Exception { + mServiceContext.setPermission( + android.Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED); + + final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); + final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build(); + mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + final String redirectUrl = "http://example.com/firstPath"; + + mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl, false /* isStrictMode */); + captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + + final CaptivePortalData testData = new CaptivePortalData.Builder() + .setUserPortalUrl(Uri.parse(redirectUrl)) + .setBytesRemaining(12345L) + .build(); + + mWiFiNetworkAgent.notifyCaptivePortalDataChanged(testData); + + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> testData.equals(lp.getCaptivePortalData())); + + final LinkProperties newLps = new LinkProperties(); + newLps.setMtu(1234); + mWiFiNetworkAgent.sendLinkProperties(newLps); + // CaptivePortalData is not lost and unchanged when LPs are received from the NetworkAgent + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> testData.equals(lp.getCaptivePortalData()) && lp.getMtu() == 1234); + } + private NetworkRequest.Builder newWifiRequestBuilder() { return new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI); } @@ -2993,12 +2985,12 @@ public class ConnectivityServiceTest { * does work. */ @Test - public void testNetworkSpecifier() { + public void testNetworkSpecifier() throws Exception { // A NetworkSpecifier subclass that matches all networks but must not be visible to apps. class ConfidentialMatchAllNetworkSpecifier extends NetworkSpecifier implements Parcelable { @Override - public boolean satisfiedBy(NetworkSpecifier other) { + public boolean canBeSatisfiedBy(NetworkSpecifier other) { return true; } @@ -3026,7 +3018,7 @@ public class ConnectivityServiceTest { } @Override - public boolean satisfiedBy(NetworkSpecifier other) { + public boolean canBeSatisfiedBy(NetworkSpecifier other) { if (other instanceof LocalStringNetworkSpecifier) { return TextUtils.equals(mString, ((LocalStringNetworkSpecifier) other).mString); @@ -3073,7 +3065,7 @@ public class ConnectivityServiceTest { LocalStringNetworkSpecifier nsFoo = new LocalStringNetworkSpecifier("foo"); LocalStringNetworkSpecifier nsBar = new LocalStringNetworkSpecifier("bar"); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); cEmpty1.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); cEmpty2.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); @@ -3084,24 +3076,24 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.setNetworkSpecifier(nsFoo); cFoo.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); for (TestNetworkCallback c: emptyCallbacks) { - c.expectCapabilitiesLike((caps) -> caps.getNetworkSpecifier().equals(nsFoo), - mWiFiNetworkAgent); + c.expectCapabilitiesThat(mWiFiNetworkAgent, + (caps) -> caps.getNetworkSpecifier().equals(nsFoo)); } - cFoo.expectCapabilitiesLike((caps) -> caps.getNetworkSpecifier().equals(nsFoo), - mWiFiNetworkAgent); + cFoo.expectCapabilitiesThat(mWiFiNetworkAgent, + (caps) -> caps.getNetworkSpecifier().equals(nsFoo)); assertEquals(nsFoo, mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier()); cFoo.assertNoCallback(); mWiFiNetworkAgent.setNetworkSpecifier(nsBar); - cFoo.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + cFoo.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); cBar.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); for (TestNetworkCallback c: emptyCallbacks) { - c.expectCapabilitiesLike((caps) -> caps.getNetworkSpecifier().equals(nsBar), - mWiFiNetworkAgent); + c.expectCapabilitiesThat(mWiFiNetworkAgent, + (caps) -> caps.getNetworkSpecifier().equals(nsBar)); } - cBar.expectCapabilitiesLike((caps) -> caps.getNetworkSpecifier().equals(nsBar), - mWiFiNetworkAgent); + cBar.expectCapabilitiesThat(mWiFiNetworkAgent, + (caps) -> caps.getNetworkSpecifier().equals(nsBar)); assertEquals(nsBar, mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier()); cBar.assertNoCallback(); @@ -3109,23 +3101,23 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.setNetworkSpecifier(new ConfidentialMatchAllNetworkSpecifier()); cFoo.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); for (TestNetworkCallback c : emptyCallbacks) { - c.expectCapabilitiesLike((caps) -> caps.getNetworkSpecifier() == null, - mWiFiNetworkAgent); + c.expectCapabilitiesThat(mWiFiNetworkAgent, + (caps) -> caps.getNetworkSpecifier() == null); } - cFoo.expectCapabilitiesLike((caps) -> caps.getNetworkSpecifier() == null, - mWiFiNetworkAgent); - cBar.expectCapabilitiesLike((caps) -> caps.getNetworkSpecifier() == null, - mWiFiNetworkAgent); + cFoo.expectCapabilitiesThat(mWiFiNetworkAgent, + (caps) -> caps.getNetworkSpecifier() == null); + cBar.expectCapabilitiesThat(mWiFiNetworkAgent, + (caps) -> caps.getNetworkSpecifier() == null); assertNull( mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier()); cFoo.assertNoCallback(); cBar.assertNoCallback(); mWiFiNetworkAgent.setNetworkSpecifier(null); - cFoo.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - cBar.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + cFoo.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + cBar.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); for (TestNetworkCallback c: emptyCallbacks) { - c.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent); + c.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mWiFiNetworkAgent); } assertNoCallbacks(cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo, cBar); @@ -3133,50 +3125,45 @@ public class ConnectivityServiceTest { @Test public void testInvalidNetworkSpecifier() { - try { + assertThrows(IllegalArgumentException.class, () -> { NetworkRequest.Builder builder = new NetworkRequest.Builder(); builder.setNetworkSpecifier(new MatchAllNetworkSpecifier()); - fail("NetworkRequest builder with MatchAllNetworkSpecifier"); - } catch (IllegalArgumentException expected) { - // expected - } + }); - try { + assertThrows(IllegalArgumentException.class, () -> { NetworkCapabilities networkCapabilities = new NetworkCapabilities(); networkCapabilities.addTransportType(TRANSPORT_WIFI) .setNetworkSpecifier(new MatchAllNetworkSpecifier()); mService.requestNetwork(networkCapabilities, null, 0, null, - ConnectivityManager.TYPE_WIFI); - fail("ConnectivityService requestNetwork with MatchAllNetworkSpecifier"); - } catch (IllegalArgumentException expected) { - // expected - } + ConnectivityManager.TYPE_WIFI, mContext.getPackageName()); + }); class NonParcelableSpecifier extends NetworkSpecifier { - public boolean satisfiedBy(NetworkSpecifier other) { return false; } + @Override + public boolean canBeSatisfiedBy(NetworkSpecifier other) { + return false; + } }; class ParcelableSpecifier extends NonParcelableSpecifier implements Parcelable { @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel p, int flags) {} } - NetworkRequest.Builder builder; - builder = new NetworkRequest.Builder().addTransportType(TRANSPORT_ETHERNET); - try { + final NetworkRequest.Builder builder = + new NetworkRequest.Builder().addTransportType(TRANSPORT_ETHERNET); + assertThrows(ClassCastException.class, () -> { builder.setNetworkSpecifier(new NonParcelableSpecifier()); Parcel parcelW = Parcel.obtain(); builder.build().writeToParcel(parcelW, 0); - fail("Parceling a non-parcelable specifier did not throw an exception"); - } catch (Exception e) { - // expected - } + }); - builder = new NetworkRequest.Builder().addTransportType(TRANSPORT_ETHERNET); - builder.setNetworkSpecifier(new ParcelableSpecifier()); - NetworkRequest nr = builder.build(); + final NetworkRequest nr = + new NetworkRequest.Builder().addTransportType(TRANSPORT_ETHERNET) + .setNetworkSpecifier(new ParcelableSpecifier()) + .build(); assertNotNull(nr); - try { + assertThrows(BadParcelableException.class, () -> { Parcel parcelW = Parcel.obtain(); nr.writeToParcel(parcelW, 0); byte[] bytes = parcelW.marshall(); @@ -3186,44 +3173,19 @@ public class ConnectivityServiceTest { parcelR.unmarshall(bytes, 0, bytes.length); parcelR.setDataPosition(0); NetworkRequest rereadNr = NetworkRequest.CREATOR.createFromParcel(parcelR); - fail("Unparceling a non-framework NetworkSpecifier did not throw an exception"); - } catch (Exception e) { - // expected - } + }); } @Test - public void testNetworkSpecifierUidSpoofSecurityException() { - class UidAwareNetworkSpecifier extends NetworkSpecifier implements Parcelable { - @Override - public boolean satisfiedBy(NetworkSpecifier other) { - return true; - } - - @Override - public void assertValidFromUid(int requestorUid) { - throw new SecurityException("failure"); - } - - @Override - public int describeContents() { return 0; } - @Override - public void writeToParcel(Parcel dest, int flags) {} - } - - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + public void testNetworkRequestUidSpoofSecurityException() throws Exception { + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); - - UidAwareNetworkSpecifier networkSpecifier = new UidAwareNetworkSpecifier(); - NetworkRequest networkRequest = newWifiRequestBuilder().setNetworkSpecifier( - networkSpecifier).build(); + NetworkRequest networkRequest = newWifiRequestBuilder().build(); TestNetworkCallback networkCallback = new TestNetworkCallback(); - try { + doThrow(new SecurityException()).when(mAppOpsManager).checkPackage(anyInt(), anyString()); + assertThrows(SecurityException.class, () -> { mCm.requestNetwork(networkRequest, networkCallback); - fail("Network request with spoofed UID did not throw a SecurityException"); - } catch (SecurityException e) { - // expected - } + }); } @Test @@ -3235,36 +3197,20 @@ public class ConnectivityServiceTest { .build(); // Registering a NetworkCallback with signal strength but w/o NETWORK_SIGNAL_STRENGTH_WAKEUP // permission should get SecurityException. - try { - mCm.registerNetworkCallback(r, new NetworkCallback()); - fail("Expected SecurityException filing a callback with signal strength"); - } catch (SecurityException expected) { - // expected - } + assertThrows(SecurityException.class, () -> + mCm.registerNetworkCallback(r, new NetworkCallback())); - try { - mCm.registerNetworkCallback(r, PendingIntent.getService( - mServiceContext, 0, new Intent(), 0)); - fail("Expected SecurityException filing a callback with signal strength"); - } catch (SecurityException expected) { - // expected - } + assertThrows(SecurityException.class, () -> + mCm.registerNetworkCallback(r, PendingIntent.getService( + mServiceContext, 0, new Intent(), 0))); // Requesting a Network with signal strength should get IllegalArgumentException. - try { - mCm.requestNetwork(r, new NetworkCallback()); - fail("Expected IllegalArgumentException filing a request with signal strength"); - } catch (IllegalArgumentException expected) { - // expected - } + assertThrows(IllegalArgumentException.class, () -> + mCm.requestNetwork(r, new NetworkCallback())); - try { - mCm.requestNetwork(r, PendingIntent.getService( - mServiceContext, 0, new Intent(), 0)); - fail("Expected IllegalArgumentException filing a request with signal strength"); - } catch (IllegalArgumentException expected) { - // expected - } + assertThrows(IllegalArgumentException.class, () -> + mCm.requestNetwork(r, PendingIntent.getService( + mServiceContext, 0, new Intent(), 0))); } @Test @@ -3283,14 +3229,14 @@ public class ConnectivityServiceTest { cellNetworkCallback.assertNoCallback(); // Bring up cell and expect CALLBACK_AVAILABLE. - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); // Bring up wifi and expect CALLBACK_AVAILABLE. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); cellNetworkCallback.assertNoCallback(); defaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); @@ -3298,12 +3244,12 @@ public class ConnectivityServiceTest { // Bring down cell. Expect no default network callback, since it wasn't the default. mCellNetworkAgent.disconnect(); - cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); defaultNetworkCallback.assertNoCallback(); assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); // Bring up cell. Expect no default network callback, since it won't be the default. - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); defaultNetworkCallback.assertNoCallback(); @@ -3313,27 +3259,20 @@ public class ConnectivityServiceTest { // followed by AVAILABLE cell. mWiFiNetworkAgent.disconnect(); cellNetworkCallback.assertNoCallback(); - defaultNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); defaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); mCellNetworkAgent.disconnect(); - cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); - defaultNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); waitForIdle(); assertEquals(null, mCm.getActiveNetwork()); - final int uid = Process.myUid(); - final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN); - final ArraySet<UidRange> ranges = new ArraySet<>(); - ranges.add(new UidRange(uid, uid)); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.setUids(ranges); - vpnNetworkAgent.connect(true); - mMockVpn.connect(); - defaultNetworkCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent); + mMockVpn.establishForMyUid(); + defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); - vpnNetworkAgent.disconnect(); - defaultNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent); + mMockVpn.disconnect(); + defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn); waitForIdle(); assertEquals(null, mCm.getActiveNetwork()); } @@ -3347,7 +3286,7 @@ public class ConnectivityServiceTest { mCm.requestNetwork(cellRequest, cellNetworkCallback); // Bring up the mobile network. - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); // We should get onAvailable(), onCapabilitiesChanged(), and @@ -3361,15 +3300,17 @@ public class ConnectivityServiceTest { lp.setInterfaceName("foonet_data0"); mCellNetworkAgent.sendLinkProperties(lp); // We should get onLinkPropertiesChanged(). - cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, + mCellNetworkAgent); cellNetworkCallback.assertNoCallback(); // Suspend the network. mCellNetworkAgent.suspend(); cellNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_SUSPENDED, mCellNetworkAgent); - cellNetworkCallback.expectCallback(CallbackState.SUSPENDED, mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackEntry.SUSPENDED, mCellNetworkAgent); cellNetworkCallback.assertNoCallback(); + assertEquals(NetworkInfo.State.SUSPENDED, mCm.getActiveNetworkInfo().getState()); // Register a garden variety default network request. TestNetworkCallback dfltNetworkCallback = new TestNetworkCallback(); @@ -3383,8 +3324,9 @@ public class ConnectivityServiceTest { mCellNetworkAgent.resume(); cellNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_SUSPENDED, mCellNetworkAgent); - cellNetworkCallback.expectCallback(CallbackState.RESUMED, mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackEntry.RESUMED, mCellNetworkAgent); cellNetworkCallback.assertNoCallback(); + assertEquals(NetworkInfo.State.CONNECTED, mCm.getActiveNetworkInfo().getState()); dfltNetworkCallback = new TestNetworkCallback(); mCm.registerDefaultNetworkCallback(dfltNetworkCallback); @@ -3416,7 +3358,7 @@ public class ConnectivityServiceTest { waitForIdle(); } - private boolean isForegroundNetwork(MockNetworkAgent network) { + private boolean isForegroundNetwork(TestNetworkAgentWrapper network) { NetworkCapabilities nc = mCm.getNetworkCapabilities(network.getNetwork()); assertNotNull(nc); return nc.hasCapability(NET_CAPABILITY_FOREGROUND); @@ -3436,21 +3378,21 @@ public class ConnectivityServiceTest { mCm.registerNetworkCallback(request, callback); mCm.registerNetworkCallback(fgRequest, fgCallback); - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); fgCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); assertTrue(isForegroundNetwork(mCellNetworkAgent)); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); // When wifi connects, cell lingers. callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); - callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); fgCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); - fgCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + fgCallback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); fgCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); assertTrue(isForegroundNetwork(mCellNetworkAgent)); assertTrue(isForegroundNetwork(mWiFiNetworkAgent)); @@ -3458,7 +3400,7 @@ public class ConnectivityServiceTest { // When lingering is complete, cell is still there but is now in the background. waitForIdle(); int timeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4; - fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent, timeoutMs); + fgCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent, timeoutMs); // Expect a network capabilities update sans FOREGROUND. callback.expectCapabilitiesWithout(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent); assertFalse(isForegroundNetwork(mCellNetworkAgent)); @@ -3469,14 +3411,11 @@ public class ConnectivityServiceTest { .addTransportType(TRANSPORT_CELLULAR).build(); final TestNetworkCallback cellCallback = new TestNetworkCallback(); mCm.requestNetwork(cellRequest, cellCallback); - // NOTE: This request causes the network's capabilities to change. This - // is currently delivered before the onAvailable() callbacks. - // TODO: Fix this. - cellCallback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent); cellCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); // Expect a network capabilities update with FOREGROUND, because the most recent // request causes its state to change. + cellCallback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent); assertTrue(isForegroundNetwork(mCellNetworkAgent)); assertTrue(isForegroundNetwork(mWiFiNetworkAgent)); @@ -3484,7 +3423,7 @@ public class ConnectivityServiceTest { // Release the request. The network immediately goes into the background, since it was not // lingering. mCm.unregisterNetworkCallback(cellCallback); - fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); + fgCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); // Expect a network capabilities update sans FOREGROUND. callback.expectCapabilitiesWithout(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent); assertFalse(isForegroundNetwork(mCellNetworkAgent)); @@ -3492,8 +3431,8 @@ public class ConnectivityServiceTest { // Disconnect wifi and check that cell is foreground again. mWiFiNetworkAgent.disconnect(); - callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - fgCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + fgCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); assertTrue(isForegroundNetwork(mCellNetworkAgent)); @@ -3529,20 +3468,20 @@ public class ConnectivityServiceTest { }; } - assertTimeLimit("Registering callbacks", REGISTER_TIME_LIMIT_MS, () -> { + assertRunsInAtMost("Registering callbacks", REGISTER_TIME_LIMIT_MS, () -> { for (NetworkCallback cb : callbacks) { mCm.registerNetworkCallback(request, cb); } }); - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); // Don't request that the network validate, because otherwise connect() will block until // the network gets NET_CAPABILITY_VALIDATED, after all the callbacks below have fired, // and we won't actually measure anything. mCellNetworkAgent.connect(false); long onAvailableDispatchingDuration = durationOf(() -> { - awaitLatch(availableLatch, 10 * CONNECT_TIME_LIMIT_MS); + await(availableLatch, 10 * CONNECT_TIME_LIMIT_MS); }); Log.d(TAG, String.format("Dispatched %d of %d onAvailable callbacks in %dms", NUM_REQUESTS - availableLatch.getCount(), NUM_REQUESTS, @@ -3552,12 +3491,12 @@ public class ConnectivityServiceTest { onAvailableDispatchingDuration <= CONNECT_TIME_LIMIT_MS); // Give wifi a high enough score that we'll linger cell when wifi comes up. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.adjustScore(40); mWiFiNetworkAgent.connect(false); long onLostDispatchingDuration = durationOf(() -> { - awaitLatch(losingLatch, 10 * SWITCH_TIME_LIMIT_MS); + await(losingLatch, 10 * SWITCH_TIME_LIMIT_MS); }); Log.d(TAG, String.format("Dispatched %d of %d onLosing callbacks in %dms", NUM_REQUESTS - losingLatch.getCount(), NUM_REQUESTS, onLostDispatchingDuration)); @@ -3565,33 +3504,13 @@ public class ConnectivityServiceTest { NUM_REQUESTS, onLostDispatchingDuration, SWITCH_TIME_LIMIT_MS), onLostDispatchingDuration <= SWITCH_TIME_LIMIT_MS); - assertTimeLimit("Unregistering callbacks", UNREGISTER_TIME_LIMIT_MS, () -> { + assertRunsInAtMost("Unregistering callbacks", UNREGISTER_TIME_LIMIT_MS, () -> { for (NetworkCallback cb : callbacks) { mCm.unregisterNetworkCallback(cb); } }); } - private long durationOf(Runnable fn) { - long startTime = SystemClock.elapsedRealtime(); - fn.run(); - return SystemClock.elapsedRealtime() - startTime; - } - - private void assertTimeLimit(String descr, long timeLimit, Runnable fn) { - long timeTaken = durationOf(fn); - String msg = String.format("%s: took %dms, limit was %dms", descr, timeTaken, timeLimit); - Log.d(TAG, msg); - assertTrue(msg, timeTaken <= timeLimit); - } - - private boolean awaitLatch(CountDownLatch l, long timeoutMs) { - try { - return l.await(timeoutMs, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) {} - return false; - } - @Test public void testMobileDataAlwaysOn() throws Exception { grantUsingBackgroundNetworksPermissionForUid(Binder.getCallingUid()); @@ -3616,7 +3535,7 @@ public class ConnectivityServiceTest { assertTrue(testFactory.getMyStartRequested()); // Bring up wifi. The factory stops looking for a network. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); // Score 60 - 40 penalty for not validated yet, then 60 when it validates testFactory.expectAddRequestsWithScores(20, 60); mWiFiNetworkAgent.connect(true); @@ -3633,7 +3552,7 @@ public class ConnectivityServiceTest { // Bring up cell data and check that the factory stops looking. assertLength(1, mCm.getAllNetworks()); - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); testFactory.expectAddRequestsWithScores(10, 50); // Unvalidated, then validated mCellNetworkAgent.connect(true); cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); @@ -3651,10 +3570,10 @@ public class ConnectivityServiceTest { testFactory.waitForNetworkRequests(1); // ... and cell data to be torn down. - cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); assertLength(1, mCm.getAllNetworks()); - testFactory.unregister(); + testFactory.terminate(); mCm.unregisterNetworkCallback(cellNetworkCallback); handlerThread.quit(); } @@ -3662,48 +3581,46 @@ public class ConnectivityServiceTest { @Test public void testAvoidBadWifiSetting() throws Exception { final ContentResolver cr = mServiceContext.getContentResolver(); - final WrappedMultinetworkPolicyTracker tracker = mService.getMultinetworkPolicyTracker(); final String settingName = Settings.Global.NETWORK_AVOID_BAD_WIFI; - tracker.configRestrictsAvoidBadWifi = false; + mPolicyTracker.mConfigRestrictsAvoidBadWifi = false; String[] values = new String[] {null, "0", "1"}; for (int i = 0; i < values.length; i++) { Settings.Global.putInt(cr, settingName, 1); - tracker.reevaluate(); + mPolicyTracker.reevaluate(); waitForIdle(); String msg = String.format("config=false, setting=%s", values[i]); assertTrue(mService.avoidBadWifi()); - assertFalse(msg, tracker.shouldNotifyWifiUnvalidated()); + assertFalse(msg, mPolicyTracker.shouldNotifyWifiUnvalidated()); } - tracker.configRestrictsAvoidBadWifi = true; + mPolicyTracker.mConfigRestrictsAvoidBadWifi = true; Settings.Global.putInt(cr, settingName, 0); - tracker.reevaluate(); + mPolicyTracker.reevaluate(); waitForIdle(); assertFalse(mService.avoidBadWifi()); - assertFalse(tracker.shouldNotifyWifiUnvalidated()); + assertFalse(mPolicyTracker.shouldNotifyWifiUnvalidated()); Settings.Global.putInt(cr, settingName, 1); - tracker.reevaluate(); + mPolicyTracker.reevaluate(); waitForIdle(); assertTrue(mService.avoidBadWifi()); - assertFalse(tracker.shouldNotifyWifiUnvalidated()); + assertFalse(mPolicyTracker.shouldNotifyWifiUnvalidated()); Settings.Global.putString(cr, settingName, null); - tracker.reevaluate(); + mPolicyTracker.reevaluate(); waitForIdle(); assertFalse(mService.avoidBadWifi()); - assertTrue(tracker.shouldNotifyWifiUnvalidated()); + assertTrue(mPolicyTracker.shouldNotifyWifiUnvalidated()); } @Test public void testAvoidBadWifi() throws Exception { final ContentResolver cr = mServiceContext.getContentResolver(); - final WrappedMultinetworkPolicyTracker tracker = mService.getMultinetworkPolicyTracker(); // Pretend we're on a carrier that restricts switching away from bad wifi. - tracker.configRestrictsAvoidBadWifi = true; + mPolicyTracker.mConfigRestrictsAvoidBadWifi = true; // File a request for cell to ensure it doesn't go down. final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); @@ -3722,27 +3639,27 @@ public class ConnectivityServiceTest { mCm.registerNetworkCallback(validatedWifiRequest, validatedWifiCallback); Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 0); - tracker.reevaluate(); + mPolicyTracker.reevaluate(); // Bring up validated cell. - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); Network cellNetwork = mCellNetworkAgent.getNetwork(); // Bring up validated wifi. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); Network wifiNetwork = mWiFiNetworkAgent.getNetwork(); // Fail validation on wifi. - mWiFiNetworkAgent.setNetworkInvalid(); + mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */); mCm.reportNetworkConnectivity(wifiNetwork, false); defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); - validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + validatedWifiCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); // Because avoid bad wifi is off, we don't switch to cellular. defaultCallback.assertNoCallback(); @@ -3754,14 +3671,14 @@ public class ConnectivityServiceTest { // Simulate switching to a carrier that does not restrict avoiding bad wifi, and expect // that we switch back to cell. - tracker.configRestrictsAvoidBadWifi = false; - tracker.reevaluate(); + mPolicyTracker.mConfigRestrictsAvoidBadWifi = false; + mPolicyTracker.reevaluate(); defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); assertEquals(mCm.getActiveNetwork(), cellNetwork); // Switch back to a restrictive carrier. - tracker.configRestrictsAvoidBadWifi = true; - tracker.reevaluate(); + mPolicyTracker.mConfigRestrictsAvoidBadWifi = true; + mPolicyTracker.reevaluate(); defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mCm.getActiveNetwork(), wifiNetwork); @@ -3776,21 +3693,21 @@ public class ConnectivityServiceTest { // Disconnect and reconnect wifi to clear the one-time switch above. mWiFiNetworkAgent.disconnect(); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); wifiNetwork = mWiFiNetworkAgent.getNetwork(); // Fail validation on wifi and expect the dialog to appear. - mWiFiNetworkAgent.setNetworkInvalid(); + mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */); mCm.reportNetworkConnectivity(wifiNetwork, false); defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); - validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + validatedWifiCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); // Simulate the user selecting "switch" and checking the don't ask again checkbox. Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1); - tracker.reevaluate(); + mPolicyTracker.reevaluate(); // We now switch to cell. defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); @@ -3803,17 +3720,17 @@ public class ConnectivityServiceTest { // Simulate the user turning the cellular fallback setting off and then on. // We switch to wifi and then to cell. Settings.Global.putString(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, null); - tracker.reevaluate(); + mPolicyTracker.reevaluate(); defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mCm.getActiveNetwork(), wifiNetwork); Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1); - tracker.reevaluate(); + mPolicyTracker.reevaluate(); defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); assertEquals(mCm.getActiveNetwork(), cellNetwork); // If cell goes down, we switch to wifi. mCellNetworkAgent.disconnect(); - defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); validatedWifiCallback.assertNoCallback(); @@ -3825,14 +3742,13 @@ public class ConnectivityServiceTest { @Test public void testMeteredMultipathPreferenceSetting() throws Exception { final ContentResolver cr = mServiceContext.getContentResolver(); - final WrappedMultinetworkPolicyTracker tracker = mService.getMultinetworkPolicyTracker(); final String settingName = Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE; for (int config : Arrays.asList(0, 3, 2)) { for (String setting: Arrays.asList(null, "0", "2", "1")) { - tracker.configMeteredMultipathPreference = config; + mPolicyTracker.mConfigMeteredMultipathPreference = config; Settings.Global.putString(cr, settingName, setting); - tracker.reevaluate(); + mPolicyTracker.reevaluate(); waitForIdle(); final int expected = (setting != null) ? Integer.parseInt(setting) : config; @@ -3847,13 +3763,13 @@ public class ConnectivityServiceTest { * time-out period expires. */ @Test - public void testSatisfiedNetworkRequestDoesNotTriggerOnUnavailable() { + public void testSatisfiedNetworkRequestDoesNotTriggerOnUnavailable() throws Exception { NetworkRequest nr = new NetworkRequest.Builder().addTransportType( NetworkCapabilities.TRANSPORT_WIFI).build(); final TestNetworkCallback networkCallback = new TestNetworkCallback(); mCm.requestNetwork(nr, networkCallback, TEST_REQUEST_TIMEOUT_MS); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, false, TEST_CALLBACK_TIMEOUT_MS); @@ -3867,18 +3783,18 @@ public class ConnectivityServiceTest { * not trigger onUnavailable() once the time-out period expires. */ @Test - public void testSatisfiedThenLostNetworkRequestDoesNotTriggerOnUnavailable() { + public void testSatisfiedThenLostNetworkRequestDoesNotTriggerOnUnavailable() throws Exception { NetworkRequest nr = new NetworkRequest.Builder().addTransportType( NetworkCapabilities.TRANSPORT_WIFI).build(); final TestNetworkCallback networkCallback = new TestNetworkCallback(); mCm.requestNetwork(nr, networkCallback, TEST_REQUEST_TIMEOUT_MS); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, false, TEST_CALLBACK_TIMEOUT_MS); mWiFiNetworkAgent.disconnect(); - networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + networkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); // Validate that UNAVAILABLE is not called networkCallback.assertNoCallback(); @@ -3890,7 +3806,7 @@ public class ConnectivityServiceTest { * (somehow) satisfied - the callback isn't called later. */ @Test - public void testTimedoutNetworkRequest() { + public void testTimedoutNetworkRequest() throws Exception { NetworkRequest nr = new NetworkRequest.Builder().addTransportType( NetworkCapabilities.TRANSPORT_WIFI).build(); final TestNetworkCallback networkCallback = new TestNetworkCallback(); @@ -3898,10 +3814,10 @@ public class ConnectivityServiceTest { mCm.requestNetwork(nr, networkCallback, timeoutMs); // pass timeout and validate that UNAVAILABLE is called - networkCallback.expectCallback(CallbackState.UNAVAILABLE, null); + networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null); // create a network satisfying request - validate that request not triggered - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); networkCallback.assertNoCallback(); } @@ -3911,7 +3827,7 @@ public class ConnectivityServiceTest { * trigger the callback. */ @Test - public void testNoCallbackAfterUnregisteredNetworkRequest() { + public void testNoCallbackAfterUnregisteredNetworkRequest() throws Exception { NetworkRequest nr = new NetworkRequest.Builder().addTransportType( NetworkCapabilities.TRANSPORT_WIFI).build(); final TestNetworkCallback networkCallback = new TestNetworkCallback(); @@ -3924,7 +3840,7 @@ public class ConnectivityServiceTest { networkCallback.assertNoCallback(); // create a network satisfying request - validate that request not triggered - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); networkCallback.assertNoCallback(); } @@ -3989,7 +3905,7 @@ public class ConnectivityServiceTest { // Simulate the factory releasing the request as unfulfillable and expect onUnavailable! testFactory.triggerUnfulfillable(requests.get(newRequestId)); - networkCallback.expectCallback(CallbackState.UNAVAILABLE, null); + networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null); testFactory.waitForRequests(); // unregister network callback - a no-op (since already freed by the @@ -3997,13 +3913,13 @@ public class ConnectivityServiceTest { mCm.unregisterNetworkCallback(networkCallback); } - testFactory.unregister(); + testFactory.terminate(); handlerThread.quit(); } private static class TestKeepaliveCallback extends PacketKeepaliveCallback { - public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR }; + public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR } private class CallbackValue { public CallbackType callbackType; @@ -4051,25 +3967,19 @@ public class ConnectivityServiceTest { mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error)); } - private void expectCallback(CallbackValue callbackValue) { - try { - assertEquals( - callbackValue, - mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); - } catch (InterruptedException e) { - fail(callbackValue.callbackType + " callback not seen after " + TIMEOUT_MS + " ms"); - } + private void expectCallback(CallbackValue callbackValue) throws InterruptedException { + assertEquals(callbackValue, mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); } - public void expectStarted() { + public void expectStarted() throws Exception { expectCallback(new CallbackValue(CallbackType.ON_STARTED)); } - public void expectStopped() { + public void expectStopped() throws Exception { expectCallback(new CallbackValue(CallbackType.ON_STOPPED)); } - public void expectError(int error) { + public void expectError(int error) throws Exception { expectCallback(new CallbackValue(CallbackType.ON_ERROR, error)); } } @@ -4130,25 +4040,20 @@ public class ConnectivityServiceTest { mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error)); } - private void expectCallback(CallbackValue callbackValue) { - try { - assertEquals( - callbackValue, - mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); - } catch (InterruptedException e) { - fail(callbackValue.callbackType + " callback not seen after " + TIMEOUT_MS + " ms"); - } + private void expectCallback(CallbackValue callbackValue) throws InterruptedException { + assertEquals(callbackValue, mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + } - public void expectStarted() { + public void expectStarted() throws InterruptedException { expectCallback(new CallbackValue(CallbackType.ON_STARTED)); } - public void expectStopped() { + public void expectStopped() throws InterruptedException { expectCallback(new CallbackValue(CallbackType.ON_STOPPED)); } - public void expectError(int error) { + public void expectError(int error) throws InterruptedException { expectCallback(new CallbackValue(CallbackType.ON_ERROR, error)); } @@ -4159,16 +4064,16 @@ public class ConnectivityServiceTest { } } - private Network connectKeepaliveNetwork(LinkProperties lp) { + private Network connectKeepaliveNetwork(LinkProperties lp) throws Exception { // Ensure the network is disconnected before we do anything. if (mWiFiNetworkAgent != null) { assertNull(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork())); } - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); - ConditionVariable cv = waitForConnectivityBroadcasts(1); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); mWiFiNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); mWiFiNetworkAgent.sendLinkProperties(lp); waitForIdle(); @@ -4176,6 +4081,7 @@ public class ConnectivityServiceTest { } @Test + @FlakyTest(bugId = 140305589) public void testPacketKeepalives() throws Exception { InetAddress myIPv4 = InetAddress.getByName("192.0.2.129"); InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35"); @@ -4229,10 +4135,10 @@ public class ConnectivityServiceTest { callback.expectError(PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED); // Check that a started keepalive can be stopped. - mWiFiNetworkAgent.setStartKeepaliveError(PacketKeepalive.SUCCESS); + mWiFiNetworkAgent.setStartKeepaliveEvent(PacketKeepalive.SUCCESS); ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 12345, dstIPv4); callback.expectStarted(); - mWiFiNetworkAgent.setStopKeepaliveError(PacketKeepalive.SUCCESS); + mWiFiNetworkAgent.setStopKeepaliveEvent(PacketKeepalive.SUCCESS); ka.stop(); callback.expectStopped(); @@ -4250,7 +4156,7 @@ public class ConnectivityServiceTest { ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 12345, dstIPv4); callback.expectStarted(); mWiFiNetworkAgent.disconnect(); - waitFor(mWiFiNetworkAgent.getDisconnectedCV()); + mWiFiNetworkAgent.expectDisconnected(); callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK); // ... and that stopping it after that has no adverse effects. @@ -4261,7 +4167,7 @@ public class ConnectivityServiceTest { // Reconnect. myNet = connectKeepaliveNetwork(lp); - mWiFiNetworkAgent.setStartKeepaliveError(PacketKeepalive.SUCCESS); + mWiFiNetworkAgent.setStartKeepaliveEvent(PacketKeepalive.SUCCESS); // Check that keepalive slots start from 1 and increment. The first one gets slot 1. mWiFiNetworkAgent.setExpectedKeepaliveSlot(1); @@ -4292,13 +4198,9 @@ public class ConnectivityServiceTest { callback3.expectStopped(); } - @FunctionalInterface - private interface ThrowingConsumer<T> { - void accept(T t) throws Exception; - } - // Helper method to prepare the executor and run test - private void runTestWithSerialExecutors(ThrowingConsumer<Executor> functor) throws Exception { + private void runTestWithSerialExecutors(ExceptionUtils.ThrowingConsumer<Executor> functor) + throws Exception { final ExecutorService executorSingleThread = Executors.newSingleThreadExecutor(); final Executor executorInline = (Runnable r) -> r.run(); functor.accept(executorSingleThread); @@ -4385,12 +4287,12 @@ public class ConnectivityServiceTest { } // Check that a started keepalive can be stopped. - mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS); + mWiFiNetworkAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS); try (SocketKeepalive ka = mCm.createSocketKeepalive( myNet, testSocket, myIPv4, dstIPv4, executor, callback)) { ka.start(validKaInterval); callback.expectStarted(); - mWiFiNetworkAgent.setStopKeepaliveError(SocketKeepalive.SUCCESS); + mWiFiNetworkAgent.setStopKeepaliveEvent(SocketKeepalive.SUCCESS); ka.stop(); callback.expectStopped(); @@ -4430,7 +4332,7 @@ public class ConnectivityServiceTest { ka.start(validKaInterval); callback.expectStarted(); mWiFiNetworkAgent.disconnect(); - waitFor(mWiFiNetworkAgent.getDisconnectedCV()); + mWiFiNetworkAgent.expectDisconnected(); callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK); // ... and that stopping it after that has no adverse effects. @@ -4443,7 +4345,7 @@ public class ConnectivityServiceTest { // Reconnect. myNet = connectKeepaliveNetwork(lp); - mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS); + mWiFiNetworkAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS); // Check that keepalive slots start from 1 and increment. The first one gets slot 1. mWiFiNetworkAgent.setExpectedKeepaliveSlot(1); @@ -4480,7 +4382,7 @@ public class ConnectivityServiceTest { // assertFalse(isUdpPortInUse(srcPort2)); mWiFiNetworkAgent.disconnect(); - waitFor(mWiFiNetworkAgent.getDisconnectedCV()); + mWiFiNetworkAgent.expectDisconnected(); mWiFiNetworkAgent = null; } @@ -4556,7 +4458,7 @@ public class ConnectivityServiceTest { testSocketV6.close(); mWiFiNetworkAgent.disconnect(); - waitFor(mWiFiNetworkAgent.getDisconnectedCV()); + mWiFiNetworkAgent.expectDisconnected(); mWiFiNetworkAgent = null; } @@ -4572,8 +4474,8 @@ public class ConnectivityServiceTest { lp.addLinkAddress(new LinkAddress(myIPv4, 25)); lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254"))); Network myNet = connectKeepaliveNetwork(lp); - mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS); - mWiFiNetworkAgent.setStopKeepaliveError(SocketKeepalive.SUCCESS); + mWiFiNetworkAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS); + mWiFiNetworkAgent.setStopKeepaliveEvent(SocketKeepalive.SUCCESS); TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback(executor); @@ -4609,14 +4511,14 @@ public class ConnectivityServiceTest { // assertFalse(isUdpPortInUse(srcPort)); mWiFiNetworkAgent.disconnect(); - waitFor(mWiFiNetworkAgent.getDisconnectedCV()); + mWiFiNetworkAgent.expectDisconnected(); mWiFiNetworkAgent = null; } private static boolean isUdpPortInUse(int port) { try (DatagramSocket ignored = new DatagramSocket(port)) { return false; - } catch (IOException ignored) { + } catch (IOException alreadyInUse) { return true; } } @@ -4628,23 +4530,19 @@ public class ConnectivityServiceTest { } private static class TestNetworkPinner extends NetworkPinner { - public static boolean awaitPin(int timeoutMs) { + public static boolean awaitPin(int timeoutMs) throws InterruptedException { synchronized(sLock) { if (sNetwork == null) { - try { - sLock.wait(timeoutMs); - } catch (InterruptedException e) {} + sLock.wait(timeoutMs); } return sNetwork != null; } } - public static boolean awaitUnpin(int timeoutMs) { + public static boolean awaitUnpin(int timeoutMs) throws InterruptedException { synchronized(sLock) { if (sNetwork != null) { - try { - sLock.wait(timeoutMs); - } catch (InterruptedException e) {} + sLock.wait(timeoutMs); } return sNetwork == null; } @@ -4667,7 +4565,7 @@ public class ConnectivityServiceTest { } @Test - public void testNetworkPinner() { + public void testNetworkPinner() throws Exception { NetworkRequest wifiRequest = new NetworkRequest.Builder() .addTransportType(TRANSPORT_WIFI) .build(); @@ -4676,9 +4574,9 @@ public class ConnectivityServiceTest { TestNetworkPinner.pin(mServiceContext, wifiRequest); assertNull(mCm.getBoundNetworkForProcess()); - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); // When wi-fi connects, expect to be pinned. @@ -4691,7 +4589,7 @@ public class ConnectivityServiceTest { assertNotPinnedToWifi(); // Reconnecting does not cause the pin to come back. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); assertFalse(TestNetworkPinner.awaitPin(100)); assertNotPinnedToWifi(); @@ -4706,23 +4604,23 @@ public class ConnectivityServiceTest { assertNotPinnedToWifi(); // Disconnect cell and wifi. - ConditionVariable cv = waitForConnectivityBroadcasts(3); // cell down, wifi up, wifi down. + ExpectedBroadcast b = registerConnectivityBroadcast(3); // cell down, wifi up, wifi down. mCellNetworkAgent.disconnect(); mWiFiNetworkAgent.disconnect(); - waitFor(cv); + b.expectBroadcast(); // Pinning takes effect even if the pinned network is the default when the pin is set... TestNetworkPinner.pin(mServiceContext, wifiRequest); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); assertTrue(TestNetworkPinner.awaitPin(100)); assertPinnedToWifiWithWifiDefault(); // ... and is maintained even when that network is no longer the default. - cv = waitForConnectivityBroadcasts(1); - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + b = registerConnectivityBroadcast(1); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mCellNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); assertPinnedToWifiWithCellDefault(); } @@ -4762,25 +4660,20 @@ public class ConnectivityServiceTest { } // Test that the limit is enforced when MAX_REQUESTS simultaneous requests are added. - try { - mCm.requestNetwork(networkRequest, new NetworkCallback()); - fail("Registering " + MAX_REQUESTS + " network requests did not throw exception"); - } catch (TooManyRequestsException expected) {} - try { - mCm.registerNetworkCallback(networkRequest, new NetworkCallback()); - fail("Registering " + MAX_REQUESTS + " network callbacks did not throw exception"); - } catch (TooManyRequestsException expected) {} - try { - mCm.requestNetwork(networkRequest, - PendingIntent.getBroadcast(mContext, 0, new Intent("c"), 0)); - fail("Registering " + MAX_REQUESTS + " PendingIntent requests did not throw exception"); - } catch (TooManyRequestsException expected) {} - try { - mCm.registerNetworkCallback(networkRequest, - PendingIntent.getBroadcast(mContext, 0, new Intent("d"), 0)); - fail("Registering " + MAX_REQUESTS - + " PendingIntent callbacks did not throw exception"); - } catch (TooManyRequestsException expected) {} + assertThrows(TooManyRequestsException.class, () -> + mCm.requestNetwork(networkRequest, new NetworkCallback()) + ); + assertThrows(TooManyRequestsException.class, () -> + mCm.registerNetworkCallback(networkRequest, new NetworkCallback()) + ); + assertThrows(TooManyRequestsException.class, () -> + mCm.requestNetwork(networkRequest, + PendingIntent.getBroadcast(mContext, 0, new Intent("c"), 0)) + ); + assertThrows(TooManyRequestsException.class, () -> + mCm.registerNetworkCallback(networkRequest, + PendingIntent.getBroadcast(mContext, 0, new Intent("d"), 0)) + ); for (Object o : registered) { if (o instanceof NetworkCallback) { @@ -4824,11 +4717,11 @@ public class ConnectivityServiceTest { } @Test - public void testNetworkInfoOfTypeNone() { - ConditionVariable broadcastCV = waitForConnectivityBroadcasts(1); + public void testNetworkInfoOfTypeNone() throws Exception { + ExpectedBroadcast b = registerConnectivityBroadcast(1); verifyNoNetwork(); - MockNetworkAgent wifiAware = new MockNetworkAgent(TRANSPORT_WIFI_AWARE); + TestNetworkAgentWrapper wifiAware = new TestNetworkAgentWrapper(TRANSPORT_WIFI_AWARE); assertNull(mCm.getActiveNetworkInfo()); Network[] allNetworks = mCm.getAllNetworks(); @@ -4843,7 +4736,7 @@ public class ConnectivityServiceTest { mCm.registerNetworkCallback(request, callback); // Bring up wifi aware network. - wifiAware.connect(false, false); + wifiAware.connect(false, false, false /* isStrictMode */); callback.expectAvailableCallbacksUnvalidated(wifiAware); assertNull(mCm.getActiveNetworkInfo()); @@ -4854,13 +4747,11 @@ public class ConnectivityServiceTest { // Disconnect wifi aware network. wifiAware.disconnect(); - callback.expectCallbackLike((info) -> info.state == CallbackState.LOST, TIMEOUT_MS); + callback.expectCallbackThat(TIMEOUT_MS, (info) -> info instanceof CallbackEntry.Lost); mCm.unregisterNetworkCallback(callback); verifyNoNetwork(); - if (broadcastCV.block(10)) { - fail("expected no broadcast, but got CONNECTIVITY_ACTION broadcast"); - } + b.expectNoBroadcast(10); } @Test @@ -4871,21 +4762,21 @@ public class ConnectivityServiceTest { assertNull(mCm.getLinkProperties(TYPE_NONE)); assertFalse(mCm.isNetworkSupported(TYPE_NONE)); - assertException(() -> { mCm.networkCapabilitiesForType(TYPE_NONE); }, - IllegalArgumentException.class); + assertThrows(IllegalArgumentException.class, + () -> mCm.networkCapabilitiesForType(TYPE_NONE)); Class<UnsupportedOperationException> unsupported = UnsupportedOperationException.class; - assertException(() -> { mCm.startUsingNetworkFeature(TYPE_WIFI, ""); }, unsupported); - assertException(() -> { mCm.stopUsingNetworkFeature(TYPE_WIFI, ""); }, unsupported); + assertThrows(unsupported, () -> mCm.startUsingNetworkFeature(TYPE_WIFI, "")); + assertThrows(unsupported, () -> mCm.stopUsingNetworkFeature(TYPE_WIFI, "")); // TODO: let test context have configuration application target sdk version // and test that pre-M requesting for TYPE_NONE sends back APN_REQUEST_FAILED - assertException(() -> { mCm.startUsingNetworkFeature(TYPE_NONE, ""); }, unsupported); - assertException(() -> { mCm.stopUsingNetworkFeature(TYPE_NONE, ""); }, unsupported); - assertException(() -> { mCm.requestRouteToHostAddress(TYPE_NONE, null); }, unsupported); + assertThrows(unsupported, () -> mCm.startUsingNetworkFeature(TYPE_NONE, "")); + assertThrows(unsupported, () -> mCm.stopUsingNetworkFeature(TYPE_NONE, "")); + assertThrows(unsupported, () -> mCm.requestRouteToHostAddress(TYPE_NONE, null)); } @Test - public void testLinkPropertiesEnsuresDirectlyConnectedRoutes() { + public void testLinkPropertiesEnsuresDirectlyConnectedRoutes() throws Exception { final NetworkRequest networkRequest = new NetworkRequest.Builder() .addTransportType(TRANSPORT_WIFI).build(); final TestNetworkCallback networkCallback = new TestNetworkCallback(); @@ -4901,16 +4792,17 @@ public class ConnectivityServiceTest { // Verify direct routes are added when network agent is first registered in // ConnectivityService. - MockNetworkAgent networkAgent = new MockNetworkAgent(TRANSPORT_WIFI, lp); + TestNetworkAgentWrapper networkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp); networkAgent.connect(true); - networkCallback.expectCallback(CallbackState.AVAILABLE, networkAgent); - networkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, networkAgent); - CallbackInfo cbi = networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, + networkCallback.expectCallback(CallbackEntry.AVAILABLE, networkAgent); + networkCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, networkAgent); + CallbackEntry.LinkPropertiesChanged cbi = + networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, networkAgent); - networkCallback.expectCallback(CallbackState.BLOCKED_STATUS, networkAgent); + networkCallback.expectCallback(CallbackEntry.BLOCKED_STATUS, networkAgent); networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, networkAgent); networkCallback.assertNoCallback(); - checkDirectlyConnectedRoutes(cbi.arg, Arrays.asList(myIpv4Address), + checkDirectlyConnectedRoutes(cbi.getLp(), Arrays.asList(myIpv4Address), Arrays.asList(myIpv4DefaultRoute)); checkDirectlyConnectedRoutes(mCm.getLinkProperties(networkAgent.getNetwork()), Arrays.asList(myIpv4Address), Arrays.asList(myIpv4DefaultRoute)); @@ -4922,21 +4814,60 @@ public class ConnectivityServiceTest { newLp.addLinkAddress(myIpv6Address1); newLp.addLinkAddress(myIpv6Address2); networkAgent.sendLinkProperties(newLp); - cbi = networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, networkAgent); + cbi = networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, networkAgent); networkCallback.assertNoCallback(); - checkDirectlyConnectedRoutes(cbi.arg, + checkDirectlyConnectedRoutes(cbi.getLp(), Arrays.asList(myIpv4Address, myIpv6Address1, myIpv6Address2), Arrays.asList(myIpv4DefaultRoute)); mCm.unregisterNetworkCallback(networkCallback); } + private <T> void assertSameElementsNoDuplicates(T[] expected, T[] actual) { + // Easier to implement than a proper "assertSameElements" method that also correctly deals + // with duplicates. + final String msg = Arrays.toString(expected) + " != " + Arrays.toString(actual); + assertEquals(msg, expected.length, actual.length); + Set expectedSet = new ArraySet<>(Arrays.asList(expected)); + assertEquals("expected contains duplicates", expectedSet.size(), expected.length); + // actual cannot have duplicates because it's the same length and has the same elements. + Set actualSet = new ArraySet<>(Arrays.asList(actual)); + assertEquals(expectedSet, actualSet); + } + + private void expectForceUpdateIfaces(Network[] networks, String defaultIface, + Integer vpnUid, String vpnIfname, String[] underlyingIfaces) throws Exception { + ArgumentCaptor<Network[]> networksCaptor = ArgumentCaptor.forClass(Network[].class); + ArgumentCaptor<VpnInfo[]> vpnInfosCaptor = ArgumentCaptor.forClass(VpnInfo[].class); + + verify(mStatsService, atLeastOnce()).forceUpdateIfaces(networksCaptor.capture(), + any(NetworkState[].class), eq(defaultIface), vpnInfosCaptor.capture()); + + assertSameElementsNoDuplicates(networksCaptor.getValue(), networks); + + VpnInfo[] infos = vpnInfosCaptor.getValue(); + if (vpnUid != null) { + assertEquals("Should have exactly one VPN:", 1, infos.length); + VpnInfo info = infos[0]; + assertEquals("Unexpected VPN owner:", (int) vpnUid, info.ownerUid); + assertEquals("Unexpected VPN interface:", vpnIfname, info.vpnIface); + assertSameElementsNoDuplicates(underlyingIfaces, info.underlyingIfaces); + } else { + assertEquals(0, infos.length); + return; + } + } + + private void expectForceUpdateIfaces(Network[] networks, String defaultIface) throws Exception { + expectForceUpdateIfaces(networks, defaultIface, null, null, new String[0]); + } + @Test public void testStatsIfacesChanged() throws Exception { - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); - Network[] onlyCell = new Network[] {mCellNetworkAgent.getNetwork()}; - Network[] onlyWifi = new Network[] {mWiFiNetworkAgent.getNetwork()}; + final Network[] onlyCell = new Network[] {mCellNetworkAgent.getNetwork()}; + final Network[] onlyWifi = new Network[] {mWiFiNetworkAgent.getNetwork()}; LinkProperties cellLp = new LinkProperties(); cellLp.setInterfaceName(MOBILE_IFNAME); @@ -4947,12 +4878,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent.connect(false); mCellNetworkAgent.sendLinkProperties(cellLp); waitForIdle(); - verify(mStatsService, atLeastOnce()) - .forceUpdateIfaces( - eq(onlyCell), - any(NetworkState[].class), - eq(MOBILE_IFNAME)); - assertEquals(new VpnInfo[0], NetworkStatsFactory.getVpnInfos()); + expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME); reset(mStatsService); // Default network switch should update ifaces. @@ -4960,66 +4886,154 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.sendLinkProperties(wifiLp); waitForIdle(); assertEquals(wifiLp, mService.getActiveLinkProperties()); - verify(mStatsService, atLeastOnce()) - .forceUpdateIfaces( - eq(onlyWifi), - any(NetworkState[].class), - eq(WIFI_IFNAME)); - assertEquals(new VpnInfo[0], NetworkStatsFactory.getVpnInfos()); + expectForceUpdateIfaces(onlyWifi, WIFI_IFNAME); reset(mStatsService); // Disconnect should update ifaces. mWiFiNetworkAgent.disconnect(); waitForIdle(); - verify(mStatsService, atLeastOnce()) - .forceUpdateIfaces( - eq(onlyCell), - any(NetworkState[].class), - eq(MOBILE_IFNAME)); - assertEquals(new VpnInfo[0], NetworkStatsFactory.getVpnInfos()); + expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME); reset(mStatsService); // Metered change should update ifaces mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); waitForIdle(); - verify(mStatsService, atLeastOnce()) - .forceUpdateIfaces( - eq(onlyCell), - any(NetworkState[].class), - eq(MOBILE_IFNAME)); - assertEquals(new VpnInfo[0], NetworkStatsFactory.getVpnInfos()); + expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME); reset(mStatsService); mCellNetworkAgent.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); waitForIdle(); - verify(mStatsService, atLeastOnce()) - .forceUpdateIfaces( - eq(onlyCell), - any(NetworkState[].class), - eq(MOBILE_IFNAME)); - assertEquals(new VpnInfo[0], NetworkStatsFactory.getVpnInfos()); + expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME); reset(mStatsService); // Captive portal change shouldn't update ifaces mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL); waitForIdle(); verify(mStatsService, never()) - .forceUpdateIfaces( - eq(onlyCell), - any(NetworkState[].class), - eq(MOBILE_IFNAME)); - assertEquals(new VpnInfo[0], NetworkStatsFactory.getVpnInfos()); + .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME), + eq(new VpnInfo[0])); reset(mStatsService); // Roaming change should update ifaces mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); waitForIdle(); - verify(mStatsService, atLeastOnce()) - .forceUpdateIfaces( - eq(onlyCell), - any(NetworkState[].class), - eq(MOBILE_IFNAME)); - assertEquals(new VpnInfo[0], NetworkStatsFactory.getVpnInfos()); + expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME); + reset(mStatsService); + + // Test VPNs. + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(VPN_IFNAME); + + mMockVpn.establishForMyUid(lp); + + final Network[] cellAndVpn = new Network[] { + mCellNetworkAgent.getNetwork(), mMockVpn.getNetwork()}; + + // A VPN with default (null) underlying networks sets the underlying network's interfaces... + expectForceUpdateIfaces(cellAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + new String[]{MOBILE_IFNAME}); + + // ...and updates them as the default network switches. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + mWiFiNetworkAgent.sendLinkProperties(wifiLp); + final Network[] onlyNull = new Network[]{null}; + final Network[] wifiAndVpn = new Network[] { + mWiFiNetworkAgent.getNetwork(), mMockVpn.getNetwork()}; + final Network[] cellAndWifi = new Network[] { + mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork()}; + final Network[] cellNullAndWifi = new Network[] { + mCellNetworkAgent.getNetwork(), null, mWiFiNetworkAgent.getNetwork()}; + + waitForIdle(); + assertEquals(wifiLp, mService.getActiveLinkProperties()); + expectForceUpdateIfaces(wifiAndVpn, WIFI_IFNAME, Process.myUid(), VPN_IFNAME, + new String[]{WIFI_IFNAME}); + reset(mStatsService); + + // A VPN that sets its underlying networks passes the underlying interfaces, and influences + // the default interface sent to NetworkStatsService by virtue of applying to the system + // server UID (or, in this test, to the test's UID). This is the reason for sending + // MOBILE_IFNAME even though the default network is wifi. + // TODO: fix this to pass in the actual default network interface. Whether or not the VPN + // applies to the system server UID should not have any bearing on network stats. + mService.setUnderlyingNetworksForVpn(onlyCell); + waitForIdle(); + expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + new String[]{MOBILE_IFNAME}); + reset(mStatsService); + + mService.setUnderlyingNetworksForVpn(cellAndWifi); + waitForIdle(); + expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + new String[]{MOBILE_IFNAME, WIFI_IFNAME}); + reset(mStatsService); + + // Null underlying networks are ignored. + mService.setUnderlyingNetworksForVpn(cellNullAndWifi); + waitForIdle(); + expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + new String[]{MOBILE_IFNAME, WIFI_IFNAME}); + reset(mStatsService); + + // If an underlying network disconnects, that interface should no longer be underlying. + // This doesn't actually work because disconnectAndDestroyNetwork only notifies + // NetworkStatsService before the underlying network is actually removed. So the underlying + // network will only be removed if notifyIfacesChangedForNetworkStats is called again. This + // could result in incorrect data usage measurements if the interface used by the + // disconnected network is reused by a system component that does not register an agent for + // it (e.g., tethering). + mCellNetworkAgent.disconnect(); + waitForIdle(); + assertNull(mService.getLinkProperties(mCellNetworkAgent.getNetwork())); + expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + new String[]{MOBILE_IFNAME, WIFI_IFNAME}); + + // Confirm that we never tell NetworkStatsService that cell is no longer the underlying + // network for the VPN... + verify(mStatsService, never()).forceUpdateIfaces(any(Network[].class), + any(NetworkState[].class), any() /* anyString() doesn't match null */, + argThat(infos -> infos[0].underlyingIfaces.length == 1 + && WIFI_IFNAME.equals(infos[0].underlyingIfaces[0]))); + verifyNoMoreInteractions(mStatsService); + reset(mStatsService); + + // ... but if something else happens that causes notifyIfacesChangedForNetworkStats to be + // called again, it does. For example, connect Ethernet, but with a low score, such that it + // does not become the default network. + mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET); + mEthernetNetworkAgent.adjustScore(-40); + mEthernetNetworkAgent.connect(false); + waitForIdle(); + verify(mStatsService).forceUpdateIfaces(any(Network[].class), + any(NetworkState[].class), any() /* anyString() doesn't match null */, + argThat(vpnInfos -> vpnInfos[0].underlyingIfaces.length == 1 + && WIFI_IFNAME.equals(vpnInfos[0].underlyingIfaces[0]))); + mEthernetNetworkAgent.disconnect(); + waitForIdle(); + reset(mStatsService); + + // When a VPN declares no underlying networks (i.e., no connectivity), getAllVpnInfo + // does not return the VPN, so CS does not pass it to NetworkStatsService. This causes + // NetworkStatsFactory#adjustForTunAnd464Xlat not to attempt any VPN data migration, which + // is probably a performance improvement (though it's very unlikely that a VPN would declare + // no underlying networks). + // Also, for the same reason as above, the active interface passed in is null. + mService.setUnderlyingNetworksForVpn(new Network[0]); + waitForIdle(); + expectForceUpdateIfaces(wifiAndVpn, null); + reset(mStatsService); + + // Specifying only a null underlying network is the same as no networks. + mService.setUnderlyingNetworksForVpn(onlyNull); + waitForIdle(); + expectForceUpdateIfaces(wifiAndVpn, null); + reset(mStatsService); + + // Specifying networks that are all disconnected is the same as specifying no networks. + mService.setUnderlyingNetworksForVpn(onlyCell); + waitForIdle(); + expectForceUpdateIfaces(wifiAndVpn, null); reset(mStatsService); } @@ -5030,7 +5044,7 @@ public class ConnectivityServiceTest { // Clear any interactions that occur as a result of CS starting up. reset(mMockDnsResolver); - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); waitForIdle(); verify(mMockDnsResolver, never()).setResolverConfiguration(any()); verifyNoMoreInteractions(mMockDnsResolver); @@ -5101,6 +5115,67 @@ public class ConnectivityServiceTest { } @Test + public void testDnsConfigurationTransTypesPushed() throws Exception { + // Clear any interactions that occur as a result of CS starting up. + reset(mMockDnsResolver); + + final NetworkRequest request = new NetworkRequest.Builder() + .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET) + .build(); + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerNetworkCallback(request, callback); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + verify(mMockDnsResolver, times(1)).createNetworkCache( + eq(mWiFiNetworkAgent.getNetwork().netId)); + verify(mMockDnsResolver, times(2)).setResolverConfiguration( + mResolverParamsParcelCaptor.capture()); + final ResolverParamsParcel resolverParams = mResolverParamsParcelCaptor.getValue(); + assertContainsExactly(resolverParams.transportTypes, TRANSPORT_WIFI); + reset(mMockDnsResolver); + } + + @Test + public void testPrivateDnsNotification() throws Exception { + NetworkRequest request = new NetworkRequest.Builder() + .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET) + .build(); + TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerNetworkCallback(request, callback); + // Bring up wifi. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + // Private DNS resolution failed, checking if the notification will be shown or not. + mWiFiNetworkAgent.setNetworkInvalid(true /* isStrictMode */); + mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid()); + waitForIdle(); + // If network validation failed, NetworkMonitor will re-evaluate the network. + // ConnectivityService should filter the redundant notification. This part is trying to + // simulate that situation and check if ConnectivityService could filter that case. + mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid()); + waitForIdle(); + verify(mNotificationManager, timeout(TIMEOUT_MS).times(1)).notifyAsUser(anyString(), + eq(NotificationType.PRIVATE_DNS_BROKEN.eventId), any(), eq(UserHandle.ALL)); + // If private DNS resolution successful, the PRIVATE_DNS_BROKEN notification shouldn't be + // shown. + mWiFiNetworkAgent.setNetworkValid(true /* isStrictMode */); + mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid()); + waitForIdle(); + verify(mNotificationManager, timeout(TIMEOUT_MS).times(1)).cancelAsUser(anyString(), + eq(NotificationType.PRIVATE_DNS_BROKEN.eventId), eq(UserHandle.ALL)); + // If private DNS resolution failed again, the PRIVATE_DNS_BROKEN notification should be + // shown again. + mWiFiNetworkAgent.setNetworkInvalid(true /* isStrictMode */); + mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid()); + waitForIdle(); + verify(mNotificationManager, timeout(TIMEOUT_MS).times(2)).notifyAsUser(anyString(), + eq(NotificationType.PRIVATE_DNS_BROKEN.eventId), any(), eq(UserHandle.ALL)); + } + + @Test public void testPrivateDnsSettingsChange() throws Exception { // Clear any interactions that occur as a result of CS starting up. reset(mMockDnsResolver); @@ -5113,7 +5188,7 @@ public class ConnectivityServiceTest { .addTransportType(TRANSPORT_CELLULAR).build(); mCm.requestNetwork(cellRequest, cellNetworkCallback); - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); waitForIdle(); // CS tells netd about the empty DNS config for this network. verify(mMockDnsResolver, never()).setResolverConfiguration(any()); @@ -5143,21 +5218,21 @@ public class ConnectivityServiceTest { ResolverParamsParcel resolvrParams = mResolverParamsParcelCaptor.getValue(); assertEquals(2, resolvrParams.tlsServers.length); assertTrue(ArrayUtils.containsAll(resolvrParams.tlsServers, - new String[]{"2001:db8::1", "192.0.2.1"})); + new String[] { "2001:db8::1", "192.0.2.1" })); // Opportunistic mode. assertEquals(2, resolvrParams.tlsServers.length); assertTrue(ArrayUtils.containsAll(resolvrParams.tlsServers, - new String[]{"2001:db8::1", "192.0.2.1"})); + new String[] { "2001:db8::1", "192.0.2.1" })); reset(mMockDnsResolver); - cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent); - cellNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, + cellNetworkCallback.expectCallback(CallbackEntry.AVAILABLE, mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mCellNetworkAgent); - CallbackInfo cbi = cellNetworkCallback.expectCallback( - CallbackState.LINK_PROPERTIES, mCellNetworkAgent); - cellNetworkCallback.expectCallback(CallbackState.BLOCKED_STATUS, mCellNetworkAgent); + CallbackEntry.LinkPropertiesChanged cbi = cellNetworkCallback.expectCallback( + CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackEntry.BLOCKED_STATUS, mCellNetworkAgent); cellNetworkCallback.assertNoCallback(); - assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive()); - assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName()); + assertFalse(cbi.getLp().isPrivateDnsActive()); + assertNull(cbi.getLp().getPrivateDnsServerName()); setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com"); verify(mMockDnsResolver, times(1)).setResolverConfiguration( @@ -5165,7 +5240,7 @@ public class ConnectivityServiceTest { resolvrParams = mResolverParamsParcelCaptor.getValue(); assertEquals(2, resolvrParams.servers.length); assertTrue(ArrayUtils.containsAll(resolvrParams.servers, - new String[]{"2001:db8::1", "192.0.2.1"})); + new String[] { "2001:db8::1", "192.0.2.1" })); reset(mMockDnsResolver); cellNetworkCallback.assertNoCallback(); @@ -5175,21 +5250,21 @@ public class ConnectivityServiceTest { resolvrParams = mResolverParamsParcelCaptor.getValue(); assertEquals(2, resolvrParams.servers.length); assertTrue(ArrayUtils.containsAll(resolvrParams.servers, - new String[]{"2001:db8::1", "192.0.2.1"})); + new String[] { "2001:db8::1", "192.0.2.1" })); assertEquals(2, resolvrParams.tlsServers.length); assertTrue(ArrayUtils.containsAll(resolvrParams.tlsServers, - new String[]{"2001:db8::1", "192.0.2.1"})); + new String[] { "2001:db8::1", "192.0.2.1" })); reset(mMockDnsResolver); cellNetworkCallback.assertNoCallback(); setPrivateDnsSettings(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, "strict.example.com"); // Can't test dns configuration for strict mode without properly mocking // out the DNS lookups, but can test that LinkProperties is updated. - cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, + cbi = cellNetworkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); cellNetworkCallback.assertNoCallback(); - assertTrue(((LinkProperties)cbi.arg).isPrivateDnsActive()); - assertEquals("strict.example.com", ((LinkProperties)cbi.arg).getPrivateDnsServerName()); + assertTrue(cbi.getLp().isPrivateDnsActive()); + assertEquals("strict.example.com", cbi.getLp().getPrivateDnsServerName()); } @Test @@ -5202,23 +5277,23 @@ public class ConnectivityServiceTest { .addTransportType(TRANSPORT_CELLULAR).build(); mCm.requestNetwork(cellRequest, cellNetworkCallback); - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); waitForIdle(); LinkProperties lp = new LinkProperties(); mCellNetworkAgent.sendLinkProperties(lp); mCellNetworkAgent.connect(false); waitForIdle(); - cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent); - cellNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, + cellNetworkCallback.expectCallback(CallbackEntry.AVAILABLE, mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mCellNetworkAgent); - CallbackInfo cbi = cellNetworkCallback.expectCallback( - CallbackState.LINK_PROPERTIES, mCellNetworkAgent); - cellNetworkCallback.expectCallback(CallbackState.BLOCKED_STATUS, mCellNetworkAgent); + CallbackEntry.LinkPropertiesChanged cbi = cellNetworkCallback.expectCallback( + CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackEntry.BLOCKED_STATUS, mCellNetworkAgent); cellNetworkCallback.assertNoCallback(); - assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive()); - assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName()); + assertFalse(cbi.getLp().isPrivateDnsActive()); + assertNull(cbi.getLp().getPrivateDnsServerName()); Set<InetAddress> dnsServers = new HashSet<>(); - checkDnsServers(cbi.arg, dnsServers); + checkDnsServers(cbi.getLp(), dnsServers); // Send a validation event for a server that is not part of the current // resolver config. The validation event should be ignored. @@ -5230,13 +5305,13 @@ public class ConnectivityServiceTest { LinkProperties lp2 = new LinkProperties(lp); lp2.addDnsServer(InetAddress.getByName("145.100.185.16")); mCellNetworkAgent.sendLinkProperties(lp2); - cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, + cbi = cellNetworkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); cellNetworkCallback.assertNoCallback(); - assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive()); - assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName()); + assertFalse(cbi.getLp().isPrivateDnsActive()); + assertNull(cbi.getLp().getPrivateDnsServerName()); dnsServers.add(InetAddress.getByName("145.100.185.16")); - checkDnsServers(cbi.arg, dnsServers); + checkDnsServers(cbi.getLp(), dnsServers); // Send a validation event containing a hostname that is not part of // the current resolver config. The validation event should be ignored. @@ -5254,39 +5329,39 @@ public class ConnectivityServiceTest { // private dns fields should be sent. mService.mNetdEventCallback.onPrivateDnsValidationEvent( mCellNetworkAgent.getNetwork().netId, "145.100.185.16", "", true); - cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, + cbi = cellNetworkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); cellNetworkCallback.assertNoCallback(); - assertTrue(((LinkProperties)cbi.arg).isPrivateDnsActive()); - assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName()); - checkDnsServers(cbi.arg, dnsServers); + assertTrue(cbi.getLp().isPrivateDnsActive()); + assertNull(cbi.getLp().getPrivateDnsServerName()); + checkDnsServers(cbi.getLp(), dnsServers); // The private dns fields in LinkProperties should be preserved when // the network agent sends unrelated changes. LinkProperties lp3 = new LinkProperties(lp2); lp3.setMtu(1300); mCellNetworkAgent.sendLinkProperties(lp3); - cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, + cbi = cellNetworkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); cellNetworkCallback.assertNoCallback(); - assertTrue(((LinkProperties)cbi.arg).isPrivateDnsActive()); - assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName()); - checkDnsServers(cbi.arg, dnsServers); - assertEquals(1300, ((LinkProperties)cbi.arg).getMtu()); + assertTrue(cbi.getLp().isPrivateDnsActive()); + assertNull(cbi.getLp().getPrivateDnsServerName()); + checkDnsServers(cbi.getLp(), dnsServers); + assertEquals(1300, cbi.getLp().getMtu()); // Removing the only validated server should affect the private dns // fields in LinkProperties. LinkProperties lp4 = new LinkProperties(lp3); lp4.removeDnsServer(InetAddress.getByName("145.100.185.16")); mCellNetworkAgent.sendLinkProperties(lp4); - cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, + cbi = cellNetworkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); cellNetworkCallback.assertNoCallback(); - assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive()); - assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName()); + assertFalse(cbi.getLp().isPrivateDnsActive()); + assertNull(cbi.getLp().getPrivateDnsServerName()); dnsServers.remove(InetAddress.getByName("145.100.185.16")); - checkDnsServers(cbi.arg, dnsServers); - assertEquals(1300, ((LinkProperties)cbi.arg).getMtu()); + checkDnsServers(cbi.getLp(), dnsServers); + assertEquals(1300, cbi.getLp().getMtu()); } private void checkDirectlyConnectedRoutes(Object callbackObj, @@ -5313,31 +5388,178 @@ public class ConnectivityServiceTest { assertTrue(lp.getDnsServers().containsAll(dnsServers)); } - private static <T> void assertEmpty(T[] ts) { - int length = ts.length; - assertEquals("expected empty array, but length was " + length, 0, length); + @Test + public void testVpnConnectDisconnectUnderlyingNetwork() throws Exception { + final TestNetworkCallback callback = new TestNetworkCallback(); + final NetworkRequest request = new NetworkRequest.Builder() + .removeCapability(NET_CAPABILITY_NOT_VPN).build(); + + mCm.registerNetworkCallback(request, callback); + + // Bring up a VPN that specifies an underlying network that does not exist yet. + // Note: it's sort of meaningless for a VPN app to declare a network that doesn't exist yet, + // (and doing so is difficult without using reflection) but it's good to test that the code + // behaves approximately correctly. + mMockVpn.establishForMyUid(false, true, false); + final Network wifiNetwork = new Network(mNetIdManager.peekNextNetId()); + mService.setUnderlyingNetworksForVpn(new Network[]{wifiNetwork}); + callback.expectAvailableCallbacksUnvalidated(mMockVpn); + assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasTransport(TRANSPORT_VPN)); + assertFalse(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasTransport(TRANSPORT_WIFI)); + + // Make that underlying network connect, and expect to see its capabilities immediately + // reflected in the VPN's capabilities. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + assertEquals(wifiNetwork, mWiFiNetworkAgent.getNetwork()); + mWiFiNetworkAgent.connect(false); + // TODO: the callback for the VPN happens before any callbacks are called for the wifi + // network that has just connected. There appear to be two issues here: + // 1. The VPN code will accept an underlying network as soon as getNetworkCapabilities() for + // it returns non-null (which happens very early, during handleRegisterNetworkAgent). + // This is not correct because that that point the network is not connected and cannot + // pass any traffic. + // 2. When a network connects, updateNetworkInfo propagates underlying network capabilities + // before rematching networks. + // Given that this scenario can't really happen, this is probably fine for now. + callback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasTransport(TRANSPORT_VPN)); + assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasTransport(TRANSPORT_WIFI)); + + // Disconnect the network, and expect to see the VPN capabilities change accordingly. + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + callback.expectCapabilitiesThat(mMockVpn, (nc) -> + nc.getTransportTypes().length == 1 && nc.hasTransport(TRANSPORT_VPN)); + + mMockVpn.disconnect(); + mCm.unregisterNetworkCallback(callback); } - private static <T> void assertLength(int expected, T[] got) { - int length = got.length; - assertEquals(String.format("expected array of length %s, but length was %s for %s", - expected, length, Arrays.toString(got)), expected, length); + private void assertGetNetworkInfoOfGetActiveNetworkIsConnected(boolean expectedConnectivity) { + // What Chromium used to do before https://chromium-review.googlesource.com/2605304 + assertEquals("Unexpected result for getActiveNetworkInfo(getActiveNetwork())", + expectedConnectivity, mCm.getNetworkInfo(mCm.getActiveNetwork()).isConnected()); } - private static <T> void assertException(Runnable block, Class<T> expected) { - try { - block.run(); - fail("Expected exception of type " + expected); - } catch (Exception got) { - if (!got.getClass().equals(expected)) { - fail("Expected exception of type " + expected + " but got " + got); - } - return; - } + @Test + public void testVpnUnderlyingNetworkSuspended() throws Exception { + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(callback); + + // Connect a VPN. + mMockVpn.establishForMyUid(false, true, false); + callback.expectAvailableCallbacksUnvalidated(mMockVpn); + + // Connect cellular data. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(false /* validated */); + callback.expectCapabilitiesThat(mMockVpn, + nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + && nc.hasTransport(TRANSPORT_CELLULAR)); + callback.assertNoCallback(); + + assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertGetNetworkInfoOfGetActiveNetworkIsConnected(true); + + // Suspend the cellular network and expect the VPN to be suspended. + mCellNetworkAgent.suspend(); + callback.expectCapabilitiesThat(mMockVpn, + nc -> !nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + && nc.hasTransport(TRANSPORT_CELLULAR)); + callback.expectCallback(CallbackEntry.SUSPENDED, mMockVpn); + callback.assertNoCallback(); + + assertFalse(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertNetworkInfo(TYPE_MOBILE, DetailedState.SUSPENDED); + assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_VPN, DetailedState.SUSPENDED); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.SUSPENDED); + // VPN's main underlying network is suspended, so no connectivity. + assertGetNetworkInfoOfGetActiveNetworkIsConnected(false); + + // Switch to another network. The VPN should no longer be suspended. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false /* validated */); + callback.expectCapabilitiesThat(mMockVpn, + nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + && nc.hasTransport(TRANSPORT_WIFI)); + callback.expectCallback(CallbackEntry.RESUMED, mMockVpn); + callback.assertNoCallback(); + + assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); + assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + assertGetNetworkInfoOfGetActiveNetworkIsConnected(true); + + // Unsuspend cellular and then switch back to it. The VPN remains not suspended. + mCellNetworkAgent.resume(); + mWiFiNetworkAgent.disconnect(); + callback.expectCapabilitiesThat(mMockVpn, + nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + && nc.hasTransport(TRANSPORT_CELLULAR)); + // Spurious double callback? + callback.expectCapabilitiesThat(mMockVpn, + nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + && nc.hasTransport(TRANSPORT_CELLULAR)); + callback.assertNoCallback(); + + assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertGetNetworkInfoOfGetActiveNetworkIsConnected(true); + + // Suspend cellular and expect no connectivity. + mCellNetworkAgent.suspend(); + callback.expectCapabilitiesThat(mMockVpn, + nc -> !nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + && nc.hasTransport(TRANSPORT_CELLULAR)); + callback.expectCallback(CallbackEntry.SUSPENDED, mMockVpn); + callback.assertNoCallback(); + + assertFalse(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertNetworkInfo(TYPE_MOBILE, DetailedState.SUSPENDED); + assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_VPN, DetailedState.SUSPENDED); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.SUSPENDED); + assertGetNetworkInfoOfGetActiveNetworkIsConnected(false); + + // Resume cellular and expect that connectivity comes back. + mCellNetworkAgent.resume(); + callback.expectCapabilitiesThat(mMockVpn, + nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + && nc.hasTransport(TRANSPORT_CELLULAR)); + callback.expectCallback(CallbackEntry.RESUMED, mMockVpn); + callback.assertNoCallback(); + + assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertGetNetworkInfoOfGetActiveNetworkIsConnected(true); } @Test - public void testVpnNetworkActive() { + public void testVpnNetworkActive() throws Exception { final int uid = Process.myUid(); final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback(); @@ -5360,7 +5582,7 @@ public class ConnectivityServiceTest { mCm.registerDefaultNetworkCallback(defaultCallback); defaultCallback.assertNoCallback(); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); @@ -5370,40 +5592,32 @@ public class ConnectivityServiceTest { vpnNetworkCallback.assertNoCallback(); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); - final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN); - final ArraySet<UidRange> ranges = new ArraySet<>(); - ranges.add(new UidRange(uid, uid)); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.setUids(ranges); + final Set<UidRange> ranges = uidRangesForUid(uid); + mMockVpn.registerAgent(ranges); + mService.setUnderlyingNetworksForVpn(new Network[0]); + // VPN networks do not satisfy the default request and are automatically validated // by NetworkMonitor - assertFalse(NetworkMonitorUtils.isValidationRequired(vpnNetworkAgent.mNetworkCapabilities)); - vpnNetworkAgent.setNetworkValid(); + assertFalse(NetworkMonitorUtils.isValidationRequired( + mMockVpn.getAgent().getNetworkCapabilities())); + mMockVpn.getAgent().setNetworkValid(false /* isStrictMode */); - vpnNetworkAgent.connect(false); - mMockVpn.connect(); - mMockVpn.setUnderlyingNetworks(new Network[0]); + mMockVpn.connect(false); - genericNetworkCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent); + genericNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); genericNotVpnNetworkCallback.assertNoCallback(); wifiNetworkCallback.assertNoCallback(); - vpnNetworkCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent); - defaultCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent); - assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); - - genericNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent); - genericNotVpnNetworkCallback.assertNoCallback(); - vpnNetworkCallback.expectCapabilitiesLike(nc -> null == nc.getUids(), vpnNetworkAgent); - defaultCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent); + vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); ranges.clear(); - vpnNetworkAgent.setUids(ranges); + mMockVpn.setUids(ranges); - genericNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent); + genericNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn); genericNotVpnNetworkCallback.assertNoCallback(); wifiNetworkCallback.assertNoCallback(); - vpnNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent); + vpnNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn); // TODO : The default network callback should actually get a LOST call here (also see the // comment below for AVAILABLE). This is because ConnectivityService does not look at UID @@ -5411,35 +5625,34 @@ public class ConnectivityServiceTest { // can't currently update their UIDs without disconnecting, so this does not matter too // much, but that is the reason the test here has to check for an update to the // capabilities instead of the expected LOST then AVAILABLE. - defaultCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn); ranges.add(new UidRange(uid, uid)); mMockVpn.setUids(ranges); - vpnNetworkAgent.setUids(ranges); - genericNetworkCallback.expectAvailableCallbacksValidated(vpnNetworkAgent); + genericNetworkCallback.expectAvailableCallbacksValidated(mMockVpn); genericNotVpnNetworkCallback.assertNoCallback(); wifiNetworkCallback.assertNoCallback(); - vpnNetworkCallback.expectAvailableCallbacksValidated(vpnNetworkAgent); + vpnNetworkCallback.expectAvailableCallbacksValidated(mMockVpn); // TODO : Here like above, AVAILABLE would be correct, but because this can't actually // happen outside of the test, ConnectivityService does not rematch callbacks. - defaultCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn); mWiFiNetworkAgent.disconnect(); - genericNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - genericNotVpnNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - wifiNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + genericNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + genericNotVpnNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + wifiNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); vpnNetworkCallback.assertNoCallback(); defaultCallback.assertNoCallback(); - vpnNetworkAgent.disconnect(); + mMockVpn.disconnect(); - genericNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent); + genericNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn); genericNotVpnNetworkCallback.assertNoCallback(); wifiNetworkCallback.assertNoCallback(); - vpnNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent); - defaultCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent); + vpnNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn); + defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn); assertEquals(null, mCm.getActiveNetwork()); mCm.unregisterNetworkCallback(genericNetworkCallback); @@ -5449,61 +5662,51 @@ public class ConnectivityServiceTest { } @Test - public void testVpnWithoutInternet() { + public void testVpnWithoutInternet() throws Exception { final int uid = Process.myUid(); final TestNetworkCallback defaultCallback = new TestNetworkCallback(); mCm.registerDefaultNetworkCallback(defaultCallback); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); - MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN); - final ArraySet<UidRange> ranges = new ArraySet<>(); - ranges.add(new UidRange(uid, uid)); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.setUids(ranges); - vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */); - mMockVpn.connect(); + mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */, + false /* isStrictMode */); defaultCallback.assertNoCallback(); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); - vpnNetworkAgent.disconnect(); + mMockVpn.disconnect(); defaultCallback.assertNoCallback(); mCm.unregisterNetworkCallback(defaultCallback); } @Test - public void testVpnWithInternet() { + public void testVpnWithInternet() throws Exception { final int uid = Process.myUid(); final TestNetworkCallback defaultCallback = new TestNetworkCallback(); mCm.registerDefaultNetworkCallback(defaultCallback); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); - MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN); - final ArraySet<UidRange> ranges = new ArraySet<>(); - ranges.add(new UidRange(uid, uid)); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.setUids(ranges); - vpnNetworkAgent.connect(true /* validated */, true /* hasInternet */); - mMockVpn.connect(); + mMockVpn.establishForMyUid(true /* validated */, true /* hasInternet */, + false /* isStrictMode */); - defaultCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent); + defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); - vpnNetworkAgent.disconnect(); - defaultCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent); + mMockVpn.disconnect(); + defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn); defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); mCm.unregisterNetworkCallback(defaultCallback); @@ -5515,54 +5718,47 @@ public class ConnectivityServiceTest { mCm.registerDefaultNetworkCallback(callback); // Bring up Ethernet. - mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET); + mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET); mEthernetNetworkAgent.connect(true); callback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent); callback.assertNoCallback(); // Bring up a VPN that has the INTERNET capability, initially unvalidated. - final int uid = Process.myUid(); - final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN); - final ArraySet<UidRange> ranges = new ArraySet<>(); - ranges.add(new UidRange(uid, uid)); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.setUids(ranges); - vpnNetworkAgent.connect(false /* validated */, true /* hasInternet */); - mMockVpn.connect(); + mMockVpn.establishForMyUid(false /* validated */, true /* hasInternet */, + false /* isStrictMode */); // Even though the VPN is unvalidated, it becomes the default network for our app. - callback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent); - // TODO: this looks like a spurious callback. - callback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mMockVpn); callback.assertNoCallback(); - assertTrue(vpnNetworkAgent.getScore() > mEthernetNetworkAgent.getScore()); - assertEquals(ConnectivityConstants.VPN_DEFAULT_SCORE, vpnNetworkAgent.getScore()); - assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertTrue(mMockVpn.getAgent().getScore() > mEthernetNetworkAgent.getScore()); + assertEquals(ConnectivityConstants.VPN_DEFAULT_SCORE, mMockVpn.getAgent().getScore()); + assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); - NetworkCapabilities nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork()); + NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); assertFalse(nc.hasCapability(NET_CAPABILITY_VALIDATED)); assertTrue(nc.hasCapability(NET_CAPABILITY_INTERNET)); - assertFalse(NetworkMonitorUtils.isValidationRequired(vpnNetworkAgent.mNetworkCapabilities)); + assertFalse(NetworkMonitorUtils.isValidationRequired( + mMockVpn.getAgent().getNetworkCapabilities())); assertTrue(NetworkMonitorUtils.isPrivateDnsValidationRequired( - vpnNetworkAgent.mNetworkCapabilities)); + mMockVpn.getAgent().getNetworkCapabilities())); // Pretend that the VPN network validates. - vpnNetworkAgent.setNetworkValid(); - vpnNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid()); + mMockVpn.getAgent().setNetworkValid(false /* isStrictMode */); + mMockVpn.getAgent().mNetworkMonitor.forceReevaluation(Process.myUid()); // Expect to see the validated capability, but no other changes, because the VPN is already // the default network for the app. - callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, vpnNetworkAgent); + callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mMockVpn); callback.assertNoCallback(); - vpnNetworkAgent.disconnect(); - callback.expectCallback(CallbackState.LOST, vpnNetworkAgent); + mMockVpn.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mMockVpn); callback.expectAvailableCallbacksValidated(mEthernetNetworkAgent); } @Test - public void testVpnSetUnderlyingNetworks() { + public void testVpnStartsWithUnderlyingCaps() throws Exception { final int uid = Process.myUid(); final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback(); @@ -5570,97 +5766,208 @@ public class ConnectivityServiceTest { .removeCapability(NET_CAPABILITY_NOT_VPN) .addTransportType(TRANSPORT_VPN) .build(); + mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback); + vpnNetworkCallback.assertNoCallback(); + + // Connect cell. It will become the default network, and in the absence of setting + // underlying networks explicitly it will become the sole underlying network for the vpn. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED); + mCellNetworkAgent.connect(true); + + mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */, + false /* isStrictMode */); + + vpnNetworkCallback.expectAvailableCallbacks(mMockVpn.getNetwork(), + false /* suspended */, false /* validated */, false /* blocked */, TIMEOUT_MS); + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn.getNetwork(), TIMEOUT_MS, + nc -> nc.hasCapability(NET_CAPABILITY_VALIDATED)); + + final NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); + assertTrue(nc.hasTransport(TRANSPORT_VPN)); + assertTrue(nc.hasTransport(TRANSPORT_CELLULAR)); + assertFalse(nc.hasTransport(TRANSPORT_WIFI)); + assertTrue(nc.hasCapability(NET_CAPABILITY_VALIDATED)); + assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED)); + assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + } + + private void assertDefaultNetworkCapabilities(int userId, NetworkAgentWrapper... networks) { + final NetworkCapabilities[] defaultCaps = mService.getDefaultNetworkCapabilitiesForUser( + userId, "com.android.calling.package"); + final String defaultCapsString = Arrays.toString(defaultCaps); + assertEquals(defaultCapsString, defaultCaps.length, networks.length); + final Set<NetworkCapabilities> defaultCapsSet = new ArraySet<>(defaultCaps); + for (NetworkAgentWrapper network : networks) { + final NetworkCapabilities nc = mCm.getNetworkCapabilities(network.getNetwork()); + final String msg = "Did not find " + nc + " in " + Arrays.toString(defaultCaps); + assertTrue(msg, defaultCapsSet.contains(nc)); + } + } + + @Test + public void testVpnSetUnderlyingNetworks() throws Exception { + final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback(); + final NetworkRequest vpnNetworkRequest = new NetworkRequest.Builder() + .removeCapability(NET_CAPABILITY_NOT_VPN) + .addTransportType(TRANSPORT_VPN) + .build(); NetworkCapabilities nc; mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback); vpnNetworkCallback.assertNoCallback(); - final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN); - final ArraySet<UidRange> ranges = new ArraySet<>(); - ranges.add(new UidRange(uid, uid)); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.connect(); - mMockVpn.setUids(ranges); - vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */); + mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */, + false /* isStrictMode */); - vpnNetworkCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent); - nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork()); + vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); assertTrue(nc.hasTransport(TRANSPORT_VPN)); assertFalse(nc.hasTransport(TRANSPORT_CELLULAR)); assertFalse(nc.hasTransport(TRANSPORT_WIFI)); // For safety reasons a VPN without underlying networks is considered metered. assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED)); + // A VPN without underlying networks is not suspended. + assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + + final int userId = UserHandle.getUserId(Process.myUid()); + assertDefaultNetworkCapabilities(userId /* no networks */); // Connect cell and use it as an underlying network. - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED); mCellNetworkAgent.connect(true); mService.setUnderlyingNetworksForVpn( new Network[] { mCellNetworkAgent.getNetwork() }); - vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN) + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) - && !caps.hasCapability(NET_CAPABILITY_NOT_METERED), - vpnNetworkAgent); + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) + && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertDefaultNetworkCapabilities(userId, mCellNetworkAgent); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED); mWiFiNetworkAgent.connect(true); mService.setUnderlyingNetworksForVpn( new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() }); - vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN) + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) - && !caps.hasCapability(NET_CAPABILITY_NOT_METERED), - vpnNetworkAgent); + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) + && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); // Don't disconnect, but note the VPN is not using wifi any more. mService.setUnderlyingNetworksForVpn( new Network[] { mCellNetworkAgent.getNetwork() }); - vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN) + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) + && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) + && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + // The return value of getDefaultNetworkCapabilitiesForUser always includes the default + // network (wifi) as well as the underlying networks (cell). + assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); + + // Remove NOT_SUSPENDED from the only network and observe VPN is now suspended. + mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_SUSPENDED); + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) + && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) + && !caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + vpnNetworkCallback.expectCallback(CallbackEntry.SUSPENDED, mMockVpn); + + // Add NOT_SUSPENDED again and observe VPN is no longer suspended. + mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED); + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) - && !caps.hasCapability(NET_CAPABILITY_NOT_METERED), - vpnNetworkAgent); + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) + && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + vpnNetworkCallback.expectCallback(CallbackEntry.RESUMED, mMockVpn); - // Use Wifi but not cell. Note the VPN is now unmetered. + // Use Wifi but not cell. Note the VPN is now unmetered and not suspended. mService.setUnderlyingNetworksForVpn( new Network[] { mWiFiNetworkAgent.getNetwork() }); - vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN) + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) - && caps.hasCapability(NET_CAPABILITY_NOT_METERED), - vpnNetworkAgent); + && caps.hasCapability(NET_CAPABILITY_NOT_METERED) + && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertDefaultNetworkCapabilities(userId, mWiFiNetworkAgent); + + // Use both again. + mService.setUnderlyingNetworksForVpn( + new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() }); + + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) + && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) + && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); + + // Cell is suspended again. As WiFi is not, this should not cause a callback. + mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_SUSPENDED); + vpnNetworkCallback.assertNoCallback(); + + // Stop using WiFi. The VPN is suspended again. + mService.setUnderlyingNetworksForVpn( + new Network[] { mCellNetworkAgent.getNetwork() }); + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) + && caps.hasTransport(TRANSPORT_CELLULAR) + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) + && !caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + vpnNetworkCallback.expectCallback(CallbackEntry.SUSPENDED, mMockVpn); + assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); // Use both again. mService.setUnderlyingNetworksForVpn( new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() }); - vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN) + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) - && !caps.hasCapability(NET_CAPABILITY_NOT_METERED), - vpnNetworkAgent); + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) + && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + vpnNetworkCallback.expectCallback(CallbackEntry.RESUMED, mMockVpn); + assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); // Disconnect cell. Receive update without even removing the dead network from the // underlying networks – it's dead anyway. Not metered any more. mCellNetworkAgent.disconnect(); - vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN) + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) - && caps.hasCapability(NET_CAPABILITY_NOT_METERED), - vpnNetworkAgent); + && caps.hasCapability(NET_CAPABILITY_NOT_METERED)); + assertDefaultNetworkCapabilities(userId, mWiFiNetworkAgent); // Disconnect wifi too. No underlying networks means this is now metered. mWiFiNetworkAgent.disconnect(); - vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN) + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) && !caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) - && !caps.hasCapability(NET_CAPABILITY_NOT_METERED), - vpnNetworkAgent); + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)); + // When a network disconnects, the callbacks are fired before all state is updated, so for a + // short time, synchronous calls will behave as if the network is still connected. Wait for + // things to settle. + waitForIdle(); + assertDefaultNetworkCapabilities(userId /* no networks */); mMockVpn.disconnect(); } @Test - public void testNullUnderlyingNetworks() { + public void testNullUnderlyingNetworks() throws Exception { final int uid = Process.myUid(); final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback(); @@ -5672,16 +5979,11 @@ public class ConnectivityServiceTest { mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback); vpnNetworkCallback.assertNoCallback(); - final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN); - final ArraySet<UidRange> ranges = new ArraySet<>(); - ranges.add(new UidRange(uid, uid)); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.connect(); - mMockVpn.setUids(ranges); - vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */); + mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */, + false /* isStrictMode */); - vpnNetworkCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent); - nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork()); + vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); assertTrue(nc.hasTransport(TRANSPORT_VPN)); assertFalse(nc.hasTransport(TRANSPORT_CELLULAR)); assertFalse(nc.hasTransport(TRANSPORT_WIFI)); @@ -5690,23 +5992,23 @@ public class ConnectivityServiceTest { assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED)); // Connect to Cell; Cell is the default network. - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN) + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) - && !caps.hasCapability(NET_CAPABILITY_NOT_METERED), - vpnNetworkAgent); + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)); // Connect to WiFi; WiFi is the new default. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); mWiFiNetworkAgent.connect(true); - vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN) + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) - && caps.hasCapability(NET_CAPABILITY_NOT_METERED), - vpnNetworkAgent); + && caps.hasCapability(NET_CAPABILITY_NOT_METERED)); // Disconnect Cell. The default network did not change, so there shouldn't be any changes in // the capabilities. @@ -5715,19 +6017,19 @@ public class ConnectivityServiceTest { // Disconnect wifi too. Now we have no default network. mWiFiNetworkAgent.disconnect(); - vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN) + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) && !caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) - && !caps.hasCapability(NET_CAPABILITY_NOT_METERED), - vpnNetworkAgent); + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)); mMockVpn.disconnect(); } @Test - public void testIsActiveNetworkMeteredOverWifi() { + public void testIsActiveNetworkMeteredOverWifi() throws Exception { // Returns true by default when no network is available. assertTrue(mCm.isActiveNetworkMetered()); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); mWiFiNetworkAgent.connect(true); waitForIdle(); @@ -5736,10 +6038,10 @@ public class ConnectivityServiceTest { } @Test - public void testIsActiveNetworkMeteredOverCell() { + public void testIsActiveNetworkMeteredOverCell() throws Exception { // Returns true by default when no network is available. assertTrue(mCm.isActiveNetworkMetered()); - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED); mCellNetworkAgent.connect(true); waitForIdle(); @@ -5748,38 +6050,31 @@ public class ConnectivityServiceTest { } @Test - public void testIsActiveNetworkMeteredOverVpnTrackingPlatformDefault() { + public void testIsActiveNetworkMeteredOverVpnTrackingPlatformDefault() throws Exception { // Returns true by default when no network is available. assertTrue(mCm.isActiveNetworkMetered()); - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED); mCellNetworkAgent.connect(true); waitForIdle(); assertTrue(mCm.isActiveNetworkMetered()); // Connect VPN network. By default it is using current default network (Cell). - MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN); - final ArraySet<UidRange> ranges = new ArraySet<>(); - final int uid = Process.myUid(); - ranges.add(new UidRange(uid, uid)); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.setUids(ranges); - vpnNetworkAgent.connect(true); - mMockVpn.connect(); - waitForIdle(); + mMockVpn.establishForMyUid(); + // Ensure VPN is now the active network. - assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); // Expect VPN to be metered. assertTrue(mCm.isActiveNetworkMetered()); // Connect WiFi. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); mWiFiNetworkAgent.connect(true); waitForIdle(); // VPN should still be the active network. - assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); // Expect VPN to be unmetered as it should now be using WiFi (new default). assertFalse(mCm.isActiveNetworkMetered()); @@ -5797,38 +6092,30 @@ public class ConnectivityServiceTest { // VPN without any underlying networks is treated as metered. assertTrue(mCm.isActiveNetworkMetered()); - vpnNetworkAgent.disconnect(); mMockVpn.disconnect(); } @Test - public void testIsActiveNetworkMeteredOverVpnSpecifyingUnderlyingNetworks() { + public void testIsActiveNetworkMeteredOverVpnSpecifyingUnderlyingNetworks() throws Exception { // Returns true by default when no network is available. assertTrue(mCm.isActiveNetworkMetered()); - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED); mCellNetworkAgent.connect(true); waitForIdle(); assertTrue(mCm.isActiveNetworkMetered()); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); mWiFiNetworkAgent.connect(true); waitForIdle(); assertFalse(mCm.isActiveNetworkMetered()); // Connect VPN network. - MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN); - final ArraySet<UidRange> ranges = new ArraySet<>(); - final int uid = Process.myUid(); - ranges.add(new UidRange(uid, uid)); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.setUids(ranges); - vpnNetworkAgent.connect(true); - mMockVpn.connect(); - waitForIdle(); + mMockVpn.establishForMyUid(); + // Ensure VPN is now the active network. - assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); // VPN is using Cell mService.setUnderlyingNetworksForVpn( new Network[] { mCellNetworkAgent.getNetwork() }); @@ -5868,31 +6155,25 @@ public class ConnectivityServiceTest { // VPN without underlying networks is treated as metered. assertTrue(mCm.isActiveNetworkMetered()); - vpnNetworkAgent.disconnect(); mMockVpn.disconnect(); } @Test - public void testIsActiveNetworkMeteredOverAlwaysMeteredVpn() { + public void testIsActiveNetworkMeteredOverAlwaysMeteredVpn() throws Exception { // Returns true by default when no network is available. assertTrue(mCm.isActiveNetworkMetered()); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); mWiFiNetworkAgent.connect(true); waitForIdle(); assertFalse(mCm.isActiveNetworkMetered()); // Connect VPN network. - MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN); - final ArraySet<UidRange> ranges = new ArraySet<>(); - final int uid = Process.myUid(); - ranges.add(new UidRange(uid, uid)); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.setUids(ranges); - vpnNetworkAgent.connect(true); - mMockVpn.connectAsAlwaysMetered(); + mMockVpn.registerAgent(true /* isAlwaysMetered */, uidRangesForUid(Process.myUid()), + new LinkProperties()); + mMockVpn.connect(true); waitForIdle(); - assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); // VPN is tracking current platform default (WiFi). mService.setUnderlyingNetworksForVpn(null); @@ -5916,32 +6197,32 @@ public class ConnectivityServiceTest { assertTrue(mCm.isActiveNetworkMetered()); - vpnNetworkAgent.disconnect(); + mMockVpn.disconnect(); } @Test - public void testNetworkBlockedStatus() { + public void testNetworkBlockedStatus() throws Exception { final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); final NetworkRequest cellRequest = new NetworkRequest.Builder() .addTransportType(TRANSPORT_CELLULAR) .build(); mCm.registerNetworkCallback(cellRequest, cellNetworkCallback); - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); - mService.setUidRulesChanged(RULE_REJECT_ALL); + setUidRulesChanged(RULE_REJECT_ALL); cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); // ConnectivityService should cache it not to invoke the callback again. - mService.setUidRulesChanged(RULE_REJECT_METERED); + setUidRulesChanged(RULE_REJECT_METERED); cellNetworkCallback.assertNoCallback(); - mService.setUidRulesChanged(RULE_NONE); + setUidRulesChanged(RULE_NONE); cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); - mService.setUidRulesChanged(RULE_REJECT_METERED); + setUidRulesChanged(RULE_REJECT_METERED); cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); // Restrict the network based on UID rule and NOT_METERED capability change. @@ -5952,18 +6233,18 @@ public class ConnectivityServiceTest { cellNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, mCellNetworkAgent); cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); - mService.setUidRulesChanged(RULE_ALLOW_METERED); + setUidRulesChanged(RULE_ALLOW_METERED); cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); - mService.setUidRulesChanged(RULE_NONE); + setUidRulesChanged(RULE_NONE); cellNetworkCallback.assertNoCallback(); // Restrict the network based on BackgroundRestricted. - mService.setRestrictBackgroundChanged(true); + setRestrictBackgroundChanged(true); cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); - mService.setRestrictBackgroundChanged(true); + setRestrictBackgroundChanged(true); cellNetworkCallback.assertNoCallback(); - mService.setRestrictBackgroundChanged(false); + setRestrictBackgroundChanged(false); cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); cellNetworkCallback.assertNoCallback(); @@ -5971,30 +6252,30 @@ public class ConnectivityServiceTest { } @Test - public void testNetworkBlockedStatusBeforeAndAfterConnect() { + public void testNetworkBlockedStatusBeforeAndAfterConnect() throws Exception { final TestNetworkCallback defaultCallback = new TestNetworkCallback(); mCm.registerDefaultNetworkCallback(defaultCallback); // No Networkcallbacks invoked before any network is active. - mService.setUidRulesChanged(RULE_REJECT_ALL); - mService.setUidRulesChanged(RULE_NONE); - mService.setUidRulesChanged(RULE_REJECT_METERED); + setUidRulesChanged(RULE_REJECT_ALL); + setUidRulesChanged(RULE_NONE); + setUidRulesChanged(RULE_REJECT_METERED); defaultCallback.assertNoCallback(); - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); defaultCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mCellNetworkAgent); // Allow to use the network after switching to NOT_METERED network. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); mWiFiNetworkAgent.connect(true); defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); // Switch to METERED network. Restrict the use of the network. mWiFiNetworkAgent.disconnect(); - defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); defaultCallback.expectAvailableCallbacksValidatedAndBlocked(mCellNetworkAgent); // Network becomes NOT_METERED. @@ -6003,17 +6284,93 @@ public class ConnectivityServiceTest { defaultCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); // Verify there's no Networkcallbacks invoked after data saver on/off. - mService.setRestrictBackgroundChanged(true); - mService.setRestrictBackgroundChanged(false); + setRestrictBackgroundChanged(true); + setRestrictBackgroundChanged(false); defaultCallback.assertNoCallback(); mCellNetworkAgent.disconnect(); - defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); defaultCallback.assertNoCallback(); mCm.unregisterNetworkCallback(defaultCallback); } + private void checkNetworkInfo(NetworkInfo ni, int type, DetailedState state) { + assertNotNull(ni); + assertEquals(type, ni.getType()); + assertEquals(ConnectivityManager.getNetworkTypeName(type), state, ni.getDetailedState()); + } + + private void assertActiveNetworkInfo(int type, DetailedState state) { + checkNetworkInfo(mCm.getActiveNetworkInfo(), type, state); + } + private void assertNetworkInfo(int type, DetailedState state) { + checkNetworkInfo(mCm.getNetworkInfo(type), type, state); + } + + @Test + public final void testLoseTrusted() throws Exception { + final NetworkRequest trustedRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_TRUSTED) + .build(); + final TestNetworkCallback trustedCallback = new TestNetworkCallback(); + mCm.requestNetwork(trustedRequest, trustedCallback); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + trustedCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + verify(mNetworkManagementService).setDefaultNetId(eq(mCellNetworkAgent.getNetwork().netId)); + reset(mNetworkManagementService); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + trustedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + verify(mNetworkManagementService).setDefaultNetId(eq(mWiFiNetworkAgent.getNetwork().netId)); + reset(mNetworkManagementService); + + mWiFiNetworkAgent.removeCapability(NET_CAPABILITY_TRUSTED); + trustedCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + verify(mNetworkManagementService).setDefaultNetId(eq(mCellNetworkAgent.getNetwork().netId)); + reset(mNetworkManagementService); + + mCellNetworkAgent.removeCapability(NET_CAPABILITY_TRUSTED); + trustedCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + verify(mNetworkManagementService).clearDefaultNetId(); + + mCm.unregisterNetworkCallback(trustedCallback); + } + + @Ignore // 40%+ flakiness : figure out why and re-enable. + @Test + public final void testBatteryStatsNetworkType() throws Exception { + final LinkProperties cellLp = new LinkProperties(); + cellLp.setInterfaceName("cell0"); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp); + mCellNetworkAgent.connect(true); + waitForIdle(); + verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(), + TYPE_MOBILE); + reset(mBatteryStatsService); + + final LinkProperties wifiLp = new LinkProperties(); + wifiLp.setInterfaceName("wifi0"); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp); + mWiFiNetworkAgent.connect(true); + waitForIdle(); + verify(mBatteryStatsService).noteNetworkInterfaceType(wifiLp.getInterfaceName(), + TYPE_WIFI); + reset(mBatteryStatsService); + + mCellNetworkAgent.disconnect(); + + cellLp.setInterfaceName("wifi0"); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp); + mCellNetworkAgent.connect(true); + waitForIdle(); + verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(), + TYPE_MOBILE); + } + /** * Make simulated InterfaceConfig for Nat464Xlat to query clat lower layer info. */ @@ -6040,11 +6397,20 @@ public class ConnectivityServiceTest { } @Test - public void testStackedLinkProperties() throws UnknownHostException, RemoteException { + public void testStackedLinkProperties() throws Exception { final LinkAddress myIpv4 = new LinkAddress("1.2.3.4/24"); final LinkAddress myIpv6 = new LinkAddress("2001:db8:1::1/64"); final String kNat64PrefixString = "2001:db8:64:64:64:64::"; final IpPrefix kNat64Prefix = new IpPrefix(InetAddress.getByName(kNat64PrefixString), 96); + final String kOtherNat64PrefixString = "64:ff9b::"; + final IpPrefix kOtherNat64Prefix = new IpPrefix( + InetAddress.getByName(kOtherNat64PrefixString), 96); + final RouteInfo defaultRoute = new RouteInfo((IpPrefix) null, myIpv6.getAddress(), + MOBILE_IFNAME); + final RouteInfo ipv6Subnet = new RouteInfo(myIpv6, null, MOBILE_IFNAME); + final RouteInfo ipv4Subnet = new RouteInfo(myIpv4, null, MOBILE_IFNAME); + final RouteInfo stackedDefault = new RouteInfo((IpPrefix) null, myIpv4.getAddress(), + CLAT_PREFIX + MOBILE_IFNAME); final NetworkRequest networkRequest = new NetworkRequest.Builder() .addTransportType(TRANSPORT_CELLULAR) @@ -6054,25 +6420,27 @@ public class ConnectivityServiceTest { mCm.registerNetworkCallback(networkRequest, networkCallback); // Prepare ipv6 only link properties. - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); - final int cellNetId = mCellNetworkAgent.getNetwork().netId; final LinkProperties cellLp = new LinkProperties(); cellLp.setInterfaceName(MOBILE_IFNAME); cellLp.addLinkAddress(myIpv6); - cellLp.addRoute(new RouteInfo((IpPrefix) null, myIpv6.getAddress(), MOBILE_IFNAME)); - cellLp.addRoute(new RouteInfo(myIpv6, null, MOBILE_IFNAME)); + cellLp.addRoute(defaultRoute); + cellLp.addRoute(ipv6Subnet); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp); reset(mNetworkManagementService); reset(mMockDnsResolver); reset(mMockNetd); - when(mNetworkManagementService.getInterfaceConfig(CLAT_PREFIX + MOBILE_IFNAME)) - .thenReturn(getClatInterfaceConfig(myIpv4)); + reset(mBatteryStatsService); // Connect with ipv6 link properties. Expect prefix discovery to be started. - mCellNetworkAgent.sendLinkProperties(cellLp); mCellNetworkAgent.connect(true); + final int cellNetId = mCellNetworkAgent.getNetwork().netId; + waitForIdle(); verify(mMockNetd, times(1)).networkCreatePhysical(eq(cellNetId), anyInt()); + assertRoutesAdded(cellNetId, ipv6Subnet, defaultRoute); verify(mMockDnsResolver, times(1)).createNetworkCache(eq(cellNetId)); + verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(), + TYPE_MOBILE); networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId); @@ -6084,44 +6452,54 @@ public class ConnectivityServiceTest { // the NAT64 prefix was removed because one was never discovered. cellLp.addLinkAddress(myIpv4); mCellNetworkAgent.sendLinkProperties(cellLp); - networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); + networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); + assertRoutesAdded(cellNetId, ipv4Subnet); verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId); verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(any()); + // Make sure BatteryStats was not told about any v4- interfaces, as none should have + // come online yet. + waitForIdle(); + verify(mBatteryStatsService, never()).noteNetworkInterfaceType(startsWith("v4-"), anyInt()); + verifyNoMoreInteractions(mMockNetd); verifyNoMoreInteractions(mMockDnsResolver); + reset(mNetworkManagementService); reset(mMockNetd); reset(mMockDnsResolver); + when(mNetworkManagementService.getInterfaceConfig(CLAT_PREFIX + MOBILE_IFNAME)) + .thenReturn(getClatInterfaceConfig(myIpv4)); // Remove IPv4 address. Expect prefix discovery to be started again. cellLp.removeLinkAddress(myIpv4); - cellLp.removeRoute(new RouteInfo(myIpv4, null, MOBILE_IFNAME)); mCellNetworkAgent.sendLinkProperties(cellLp); - networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); + networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId); + assertRoutesRemoved(cellNetId, ipv4Subnet); // When NAT64 prefix discovery succeeds, LinkProperties are updated and clatd is started. - Nat464Xlat clat = mService.getNat464Xlat(mCellNetworkAgent); + Nat464Xlat clat = getNat464Xlat(mCellNetworkAgent); assertNull(mCm.getLinkProperties(mCellNetworkAgent.getNetwork()).getNat64Prefix()); mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */, kNat64PrefixString, 96); - LinkProperties lpBeforeClat = (LinkProperties) networkCallback.expectCallback( - CallbackState.LINK_PROPERTIES, mCellNetworkAgent).arg; + LinkProperties lpBeforeClat = networkCallback.expectCallback( + CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent).getLp(); assertEquals(0, lpBeforeClat.getStackedLinks().size()); assertEquals(kNat64Prefix, lpBeforeClat.getNat64Prefix()); verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString()); // Clat iface comes up. Expect stacked link to be added. clat.interfaceLinkStateChanged(CLAT_PREFIX + MOBILE_IFNAME, true); - networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); + networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); List<LinkProperties> stackedLps = mCm.getLinkProperties(mCellNetworkAgent.getNetwork()) .getStackedLinks(); assertEquals(makeClatLinkProperties(myIpv4), stackedLps.get(0)); + assertRoutesAdded(cellNetId, stackedDefault); // Change trivial linkproperties and see if stacked link is preserved. cellLp.addDnsServer(InetAddress.getByName("8.8.8.8")); mCellNetworkAgent.sendLinkProperties(cellLp); - networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); + networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); List<LinkProperties> stackedLpsAfterChange = mCm.getLinkProperties(mCellNetworkAgent.getNetwork()).getStackedLinks(); @@ -6134,22 +6512,48 @@ public class ConnectivityServiceTest { assertEquals(1, resolvrParams.servers.length); assertTrue(ArrayUtils.contains(resolvrParams.servers, "8.8.8.8")); + for (final LinkProperties stackedLp : stackedLpsAfterChange) { + verify(mBatteryStatsService).noteNetworkInterfaceType(stackedLp.getInterfaceName(), + TYPE_MOBILE); + } + reset(mMockNetd); + + // Change the NAT64 prefix without first removing it. + // Expect clatd to be stopped and started with the new prefix. + mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */, + kOtherNat64PrefixString, 96); + networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, + (lp) -> lp.getStackedLinks().size() == 0); + verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME); + assertRoutesRemoved(cellNetId, stackedDefault); + + verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kOtherNat64Prefix.toString()); + networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, + (lp) -> lp.getNat64Prefix().equals(kOtherNat64Prefix)); + clat.interfaceLinkStateChanged(CLAT_PREFIX + MOBILE_IFNAME, true); + networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, + (lp) -> lp.getStackedLinks().size() == 1); + assertRoutesAdded(cellNetId, stackedDefault); + reset(mMockNetd); + // Add ipv4 address, expect that clatd and prefix discovery are stopped and stacked // linkproperties are cleaned up. cellLp.addLinkAddress(myIpv4); - cellLp.addRoute(new RouteInfo(myIpv4, null, MOBILE_IFNAME)); + cellLp.addRoute(ipv4Subnet); mCellNetworkAgent.sendLinkProperties(cellLp); - networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); + networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); + assertRoutesAdded(cellNetId, ipv4Subnet); verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME); verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId); // As soon as stop is called, the linkproperties lose the stacked interface. - networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); + networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); LinkProperties actualLpAfterIpv4 = mCm.getLinkProperties(mCellNetworkAgent.getNetwork()); LinkProperties expected = new LinkProperties(cellLp); - expected.setNat64Prefix(kNat64Prefix); + expected.setNat64Prefix(kOtherNat64Prefix); assertEquals(expected, actualLpAfterIpv4); assertEquals(0, actualLpAfterIpv4.getStackedLinks().size()); + assertRoutesRemoved(cellNetId, stackedDefault); // The interface removed callback happens but has no effect after stop is called. clat.interfaceRemoved(CLAT_PREFIX + MOBILE_IFNAME); @@ -6157,60 +6561,238 @@ public class ConnectivityServiceTest { verifyNoMoreInteractions(mMockNetd); verifyNoMoreInteractions(mMockDnsResolver); + reset(mNetworkManagementService); reset(mMockNetd); reset(mMockDnsResolver); + when(mNetworkManagementService.getInterfaceConfig(CLAT_PREFIX + MOBILE_IFNAME)) + .thenReturn(getClatInterfaceConfig(myIpv4)); // Stopping prefix discovery causes netd to tell us that the NAT64 prefix is gone. mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, false /* added */, - kNat64PrefixString, 96); - networkCallback.expectLinkPropertiesLike((lp) -> lp.getNat64Prefix() == null, - mCellNetworkAgent); + kOtherNat64PrefixString, 96); + networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, + (lp) -> lp.getNat64Prefix() == null); // Remove IPv4 address and expect prefix discovery and clatd to be started again. cellLp.removeLinkAddress(myIpv4); cellLp.removeRoute(new RouteInfo(myIpv4, null, MOBILE_IFNAME)); cellLp.removeDnsServer(InetAddress.getByName("8.8.8.8")); mCellNetworkAgent.sendLinkProperties(cellLp); - networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); + networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); + assertRoutesRemoved(cellNetId, ipv4Subnet); // Directly-connected routes auto-added. verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId); mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */, kNat64PrefixString, 96); - networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); + networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString()); - // Clat iface comes up. Expect stacked link to be added. clat.interfaceLinkStateChanged(CLAT_PREFIX + MOBILE_IFNAME, true); - networkCallback.expectLinkPropertiesLike( - (lp) -> lp.getStackedLinks().size() == 1 && lp.getNat64Prefix() != null, - mCellNetworkAgent); + networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, + (lp) -> lp.getStackedLinks().size() == 1 && lp.getNat64Prefix() != null); + assertRoutesAdded(cellNetId, stackedDefault); // NAT64 prefix is removed. Expect that clat is stopped. mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, false /* added */, kNat64PrefixString, 96); - networkCallback.expectLinkPropertiesLike( - (lp) -> lp.getStackedLinks().size() == 0 && lp.getNat64Prefix() == null, - mCellNetworkAgent); + networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, + (lp) -> lp.getStackedLinks().size() == 0 && lp.getNat64Prefix() == null); + assertRoutesRemoved(cellNetId, ipv4Subnet, stackedDefault); + + // Stop has no effect because clat is already stopped. verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME); - networkCallback.expectLinkPropertiesLike((lp) -> lp.getStackedLinks().size() == 0, - mCellNetworkAgent); + networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, + (lp) -> lp.getStackedLinks().size() == 0); + verifyNoMoreInteractions(mMockNetd); // Clean up. mCellNetworkAgent.disconnect(); - networkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); + networkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); networkCallback.assertNoCallback(); mCm.unregisterNetworkCallback(networkCallback); } + private void expectNat64PrefixChange(TestableNetworkCallback callback, + TestNetworkAgentWrapper agent, IpPrefix prefix) { + callback.expectLinkPropertiesThat(agent, x -> Objects.equals(x.getNat64Prefix(), prefix)); + } + @Test - public void testDataActivityTracking() throws RemoteException { + public void testNat64PrefixMultipleSources() throws Exception { + final String iface = "wlan0"; + final String pref64FromRaStr = "64:ff9b::"; + final String pref64FromDnsStr = "2001:db8:64::"; + final IpPrefix pref64FromRa = new IpPrefix(InetAddress.getByName(pref64FromRaStr), 96); + final IpPrefix pref64FromDns = new IpPrefix(InetAddress.getByName(pref64FromDnsStr), 96); + final IpPrefix newPref64FromRa = new IpPrefix("2001:db8:64:64:64:64::/96"); + + final NetworkRequest request = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_INTERNET) + .build(); + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerNetworkCallback(request, callback); + + final LinkProperties baseLp = new LinkProperties(); + baseLp.setInterfaceName(iface); + baseLp.addLinkAddress(new LinkAddress("2001:db8:1::1/64")); + baseLp.addDnsServer(InetAddress.getByName("2001:4860:4860::6464")); + + reset(mMockNetd, mMockDnsResolver); + InOrder inOrder = inOrder(mMockNetd, mMockDnsResolver); + + // If a network already has a NAT64 prefix on connect, clatd is started immediately and + // prefix discovery is never started. + LinkProperties lp = new LinkProperties(baseLp); + lp.setNat64Prefix(pref64FromRa); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp); + mWiFiNetworkAgent.connect(false); + final Network network = mWiFiNetworkAgent.getNetwork(); + int netId = network.getNetId(); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + inOrder.verify(mMockNetd).clatdStart(iface, pref64FromRa.toString()); + inOrder.verify(mMockDnsResolver).setPrefix64(netId, pref64FromRa.toString()); + inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId); + callback.assertNoCallback(); + assertEquals(pref64FromRa, mCm.getLinkProperties(network).getNat64Prefix()); + + // If the RA prefix is withdrawn, clatd is stopped and prefix discovery is started. + lp.setNat64Prefix(null); + mWiFiNetworkAgent.sendLinkProperties(lp); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, null); + inOrder.verify(mMockNetd).clatdStop(iface); + inOrder.verify(mMockDnsResolver).setPrefix64(netId, ""); + inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId); + + // If the RA prefix appears while DNS discovery is in progress, discovery is stopped and + // clatd is started with the prefix from the RA. + lp.setNat64Prefix(pref64FromRa); + mWiFiNetworkAgent.sendLinkProperties(lp); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromRa); + inOrder.verify(mMockNetd).clatdStart(iface, pref64FromRa.toString()); + inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId); + inOrder.verify(mMockDnsResolver).setPrefix64(netId, pref64FromRa.toString()); + + // Withdraw the RA prefix so we can test the case where an RA prefix appears after DNS + // discovery has succeeded. + lp.setNat64Prefix(null); + mWiFiNetworkAgent.sendLinkProperties(lp); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, null); + inOrder.verify(mMockNetd).clatdStop(iface); + inOrder.verify(mMockDnsResolver).setPrefix64(netId, ""); + inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId); + + mService.mNetdEventCallback.onNat64PrefixEvent(netId, true /* added */, + pref64FromDnsStr, 96); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromDns); + inOrder.verify(mMockNetd).clatdStart(iface, pref64FromDns.toString()); + + // If an RA advertises the same prefix that was discovered by DNS, nothing happens: prefix + // discovery is not stopped, and there are no callbacks. + lp.setNat64Prefix(pref64FromDns); + mWiFiNetworkAgent.sendLinkProperties(lp); + callback.assertNoCallback(); + inOrder.verify(mMockNetd, never()).clatdStop(iface); + inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString()); + inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId); + inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId); + inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString()); + + // If the RA is later withdrawn, nothing happens again. + lp.setNat64Prefix(null); + mWiFiNetworkAgent.sendLinkProperties(lp); + callback.assertNoCallback(); + inOrder.verify(mMockNetd, never()).clatdStop(iface); + inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString()); + inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId); + inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId); + inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString()); + + // If the RA prefix changes, clatd is restarted and prefix discovery is stopped. + lp.setNat64Prefix(pref64FromRa); + mWiFiNetworkAgent.sendLinkProperties(lp); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromRa); + inOrder.verify(mMockNetd).clatdStop(iface); + inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId); + + // Stopping prefix discovery results in a prefix removed notification. + mService.mNetdEventCallback.onNat64PrefixEvent(netId, false /* added */, + pref64FromDnsStr, 96); + + inOrder.verify(mMockNetd).clatdStart(iface, pref64FromRa.toString()); + inOrder.verify(mMockDnsResolver).setPrefix64(netId, pref64FromRa.toString()); + inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId); + + // If the RA prefix changes, clatd is restarted and prefix discovery is not started. + lp.setNat64Prefix(newPref64FromRa); + mWiFiNetworkAgent.sendLinkProperties(lp); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, newPref64FromRa); + inOrder.verify(mMockNetd).clatdStop(iface); + inOrder.verify(mMockDnsResolver).setPrefix64(netId, ""); + inOrder.verify(mMockNetd).clatdStart(iface, newPref64FromRa.toString()); + inOrder.verify(mMockDnsResolver).setPrefix64(netId, newPref64FromRa.toString()); + inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId); + inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId); + + // If the RA prefix changes to the same value, nothing happens. + lp.setNat64Prefix(newPref64FromRa); + mWiFiNetworkAgent.sendLinkProperties(lp); + callback.assertNoCallback(); + assertEquals(newPref64FromRa, mCm.getLinkProperties(network).getNat64Prefix()); + inOrder.verify(mMockNetd, never()).clatdStop(iface); + inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString()); + inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId); + inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId); + inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString()); + + // The transition between no prefix and DNS prefix is tested in testStackedLinkProperties. + + // If the same prefix is learned first by DNS and then by RA, and clat is later stopped, + // (e.g., because the network disconnects) setPrefix64(netid, "") is never called. + lp.setNat64Prefix(null); + mWiFiNetworkAgent.sendLinkProperties(lp); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, null); + inOrder.verify(mMockNetd).clatdStop(iface); + inOrder.verify(mMockDnsResolver).setPrefix64(netId, ""); + inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId); + mService.mNetdEventCallback.onNat64PrefixEvent(netId, true /* added */, + pref64FromDnsStr, 96); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromDns); + inOrder.verify(mMockNetd).clatdStart(iface, pref64FromDns.toString()); + inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), any()); + + lp.setNat64Prefix(pref64FromDns); + mWiFiNetworkAgent.sendLinkProperties(lp); + callback.assertNoCallback(); + inOrder.verify(mMockNetd, never()).clatdStop(iface); + inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString()); + inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId); + inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId); + inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString()); + + // When tearing down a network, clat state is only updated after CALLBACK_LOST is fired, but + // before CONNECTIVITY_ACTION is sent. Wait for CONNECTIVITY_ACTION before verifying that + // clat has been stopped, or the test will be flaky. + ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED); + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + b.expectBroadcast(); + + inOrder.verify(mMockNetd).clatdStop(iface); + inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId); + inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString()); + + mCm.unregisterNetworkCallback(callback); + } + + @Test + public void testDataActivityTracking() throws Exception { final TestNetworkCallback networkCallback = new TestNetworkCallback(); final NetworkRequest networkRequest = new NetworkRequest.Builder() .addCapability(NET_CAPABILITY_INTERNET) .build(); mCm.registerNetworkCallback(networkRequest, networkCallback); - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); final LinkProperties cellLp = new LinkProperties(); cellLp.setInterfaceName(MOBILE_IFNAME); mCellNetworkAgent.sendLinkProperties(cellLp); @@ -6220,7 +6802,7 @@ public class ConnectivityServiceTest { verify(mNetworkManagementService, times(1)).addIdleTimer(eq(MOBILE_IFNAME), anyInt(), eq(ConnectivityManager.TYPE_MOBILE)); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); final LinkProperties wifiLp = new LinkProperties(); wifiLp.setInterfaceName(WIFI_IFNAME); mWiFiNetworkAgent.sendLinkProperties(wifiLp); @@ -6229,7 +6811,7 @@ public class ConnectivityServiceTest { reset(mNetworkManagementService); mWiFiNetworkAgent.connect(true); networkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); - networkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + networkCallback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); verify(mNetworkManagementService, times(1)).addIdleTimer(eq(WIFI_IFNAME), anyInt(), eq(ConnectivityManager.TYPE_WIFI)); @@ -6238,26 +6820,26 @@ public class ConnectivityServiceTest { // Disconnect wifi and switch back to cell reset(mNetworkManagementService); mWiFiNetworkAgent.disconnect(); - networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + networkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); assertNoCallbacks(networkCallback); verify(mNetworkManagementService, times(1)).removeIdleTimer(eq(WIFI_IFNAME)); verify(mNetworkManagementService, times(1)).addIdleTimer(eq(MOBILE_IFNAME), anyInt(), eq(ConnectivityManager.TYPE_MOBILE)); // reconnect wifi - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); wifiLp.setInterfaceName(WIFI_IFNAME); mWiFiNetworkAgent.sendLinkProperties(wifiLp); mWiFiNetworkAgent.connect(true); networkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); - networkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + networkCallback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); // Disconnect cell reset(mNetworkManagementService); reset(mMockNetd); mCellNetworkAgent.disconnect(); - networkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); + networkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); // LOST callback is triggered earlier than removing idle timer. Broadcast should also be // sent as network being switched. Ensure rule removal for cell will not be triggered // unexpectedly before network being removed. @@ -6268,59 +6850,68 @@ public class ConnectivityServiceTest { .destroyNetworkCache(eq(mCellNetworkAgent.getNetwork().netId)); // Disconnect wifi - ConditionVariable cv = waitForConnectivityBroadcasts(1); + ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED); reset(mNetworkManagementService); mWiFiNetworkAgent.disconnect(); - waitFor(cv); + b.expectBroadcast(); verify(mNetworkManagementService, times(1)).removeIdleTimer(eq(WIFI_IFNAME)); // Clean up mCm.unregisterNetworkCallback(networkCallback); } - private void verifyTcpBufferSizeChange(String tcpBufferSizes) { + private void verifyTcpBufferSizeChange(String tcpBufferSizes) throws Exception { String[] values = tcpBufferSizes.split(","); String rmemValues = String.join(" ", values[0], values[1], values[2]); String wmemValues = String.join(" ", values[3], values[4], values[5]); - waitForIdle(); - try { - verify(mMockNetd, atLeastOnce()).setTcpRWmemorySize(rmemValues, wmemValues); - } catch (RemoteException e) { - fail("mMockNetd should never throw RemoteException"); - } + verify(mMockNetd, atLeastOnce()).setTcpRWmemorySize(rmemValues, wmemValues); reset(mMockNetd); } @Test - public void testTcpBufferReset() { + public void testTcpBufferReset() throws Exception { final String testTcpBufferSizes = "1,2,3,4,5,6"; + final NetworkRequest networkRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_INTERNET) + .build(); + final TestNetworkCallback networkCallback = new TestNetworkCallback(); + mCm.registerNetworkCallback(networkRequest, networkCallback); - mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); reset(mMockNetd); // Switching default network updates TCP buffer sizes. mCellNetworkAgent.connect(false); + networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); verifyTcpBufferSizeChange(ConnectivityService.DEFAULT_TCP_BUFFER_SIZES); // Change link Properties should have updated tcp buffer size. LinkProperties lp = new LinkProperties(); lp.setTcpBufferSizes(testTcpBufferSizes); mCellNetworkAgent.sendLinkProperties(lp); + networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); verifyTcpBufferSizeChange(testTcpBufferSizes); + + // Clean up. + mCellNetworkAgent.disconnect(); + networkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + networkCallback.assertNoCallback(); + mCm.unregisterNetworkCallback(networkCallback); } @Test - public void testGetGlobalProxyForNetwork() { + public void testGetGlobalProxyForNetwork() throws Exception { final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); final Network wifiNetwork = mWiFiNetworkAgent.getNetwork(); when(mService.mProxyTracker.getGlobalProxy()).thenReturn(testProxyInfo); assertEquals(testProxyInfo, mService.getProxyForNetwork(wifiNetwork)); } @Test - public void testGetProxyForActiveNetwork() { + public void testGetProxyForActiveNetwork() throws Exception { final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); waitForIdle(); assertNull(mService.getProxyForNetwork(null)); @@ -6335,42 +6926,30 @@ public class ConnectivityServiceTest { } @Test - public void testGetProxyForVPN() { + public void testGetProxyForVPN() throws Exception { final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); // Set up a WiFi network with no proxy - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); waitForIdle(); assertNull(mService.getProxyForNetwork(null)); - // Set up a VPN network with a proxy - final int uid = Process.myUid(); - final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN); - final ArraySet<UidRange> ranges = new ArraySet<>(); - ranges.add(new UidRange(uid, uid)); - mMockVpn.setUids(ranges); + // Connect a VPN network with a proxy. LinkProperties testLinkProperties = new LinkProperties(); testLinkProperties.setHttpProxy(testProxyInfo); - vpnNetworkAgent.sendLinkProperties(testLinkProperties); - waitForIdle(); - - // Connect to VPN with proxy - mMockVpn.setNetworkAgent(vpnNetworkAgent); - vpnNetworkAgent.connect(true); - mMockVpn.connect(); - waitForIdle(); + mMockVpn.establishForMyUid(testLinkProperties); // Test that the VPN network returns a proxy, and the WiFi does not. - assertEquals(testProxyInfo, mService.getProxyForNetwork(vpnNetworkAgent.getNetwork())); + assertEquals(testProxyInfo, mService.getProxyForNetwork(mMockVpn.getNetwork())); assertEquals(testProxyInfo, mService.getProxyForNetwork(null)); assertNull(mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork())); // Test that the VPN network returns no proxy when it is set to null. testLinkProperties.setHttpProxy(null); - vpnNetworkAgent.sendLinkProperties(testLinkProperties); + mMockVpn.sendLinkProperties(testLinkProperties); waitForIdle(); - assertNull(mService.getProxyForNetwork(vpnNetworkAgent.getNetwork())); + assertNull(mService.getProxyForNetwork(mMockVpn.getNetwork())); assertNull(mService.getProxyForNetwork(null)); // Set WiFi proxy and check that the vpn proxy is still null. @@ -6381,7 +6960,7 @@ public class ConnectivityServiceTest { // Disconnect from VPN and check that the active network, which is now the WiFi, has the // correct proxy setting. - vpnNetworkAgent.disconnect(); + mMockVpn.disconnect(); waitForIdle(); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertEquals(testProxyInfo, mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork())); @@ -6393,9 +6972,10 @@ public class ConnectivityServiceTest { LinkProperties lp = new LinkProperties(); lp.setInterfaceName("tun0"); lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE)); // The uid range needs to cover the test app so the network is visible to it. final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER)); - final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange); + mMockVpn.establish(lp, VPN_UID, vpnRange); // Connected VPN should have interface rules set up. There are two expected invocations, // one during VPN uid update, one during VPN LinkProperties update @@ -6405,7 +6985,7 @@ public class ConnectivityServiceTest { assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID); assertTrue(mService.mPermissionMonitor.getVpnUidRanges("tun0").equals(vpnRange)); - vpnNetworkAgent.disconnect(); + mMockVpn.disconnect(); waitForIdle(); // Disconnected VPN should have interface rules removed @@ -6418,10 +6998,11 @@ public class ConnectivityServiceTest { public void testLegacyVpnDoesNotResultInInterfaceFilteringRule() throws Exception { LinkProperties lp = new LinkProperties(); lp.setInterfaceName("tun0"); + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); // The uid range needs to cover the test app so the network is visible to it. final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER)); - final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, Process.SYSTEM_UID, vpnRange); + mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange); // Legacy VPN should not have interface rules set up verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any()); @@ -6436,7 +7017,7 @@ public class ConnectivityServiceTest { lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE)); // The uid range needs to cover the test app so the network is visible to it. final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER)); - final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, Process.SYSTEM_UID, vpnRange); + mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange); // IPv6 unreachable route should not be misinterpreted as a default route verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any()); @@ -6447,9 +7028,10 @@ public class ConnectivityServiceTest { LinkProperties lp = new LinkProperties(); lp.setInterfaceName("tun0"); lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); // The uid range needs to cover the test app so the network is visible to it. final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER)); - final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange); + mMockVpn.establish(lp, VPN_UID, vpnRange); // Connected VPN should have interface rules set up. There are two expected invocations, // one during VPN uid update, one during VPN LinkProperties update @@ -6461,7 +7043,7 @@ public class ConnectivityServiceTest { reset(mMockNetd); InOrder inOrder = inOrder(mMockNetd); lp.setInterfaceName("tun1"); - vpnNetworkAgent.sendLinkProperties(lp); + mMockVpn.sendLinkProperties(lp); waitForIdle(); // VPN handover (switch to a new interface) should result in rules being updated (old rules // removed first, then new rules added) @@ -6474,7 +7056,7 @@ public class ConnectivityServiceTest { lp = new LinkProperties(); lp.setInterfaceName("tun1"); lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun1")); - vpnNetworkAgent.sendLinkProperties(lp); + mMockVpn.sendLinkProperties(lp); waitForIdle(); // VPN not routing everything should no longer have interface filtering rules verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture()); @@ -6483,8 +7065,9 @@ public class ConnectivityServiceTest { reset(mMockNetd); lp = new LinkProperties(); lp.setInterfaceName("tun1"); + lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); - vpnNetworkAgent.sendLinkProperties(lp); + mMockVpn.sendLinkProperties(lp); waitForIdle(); // Back to routing all IPv6 traffic should have filtering rules verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture()); @@ -6492,14 +7075,26 @@ public class ConnectivityServiceTest { } @Test + public void testStartVpnProfileFromDiffPackage() throws Exception { + final String notMyVpnPkg = "com.not.my.vpn"; + assertThrows(SecurityException.class, () -> mService.startVpnProfile(notMyVpnPkg)); + } + + @Test + public void testStopVpnProfileFromDiffPackage() throws Exception { + final String notMyVpnPkg = "com.not.my.vpn"; + assertThrows(SecurityException.class, () -> mService.stopVpnProfile(notMyVpnPkg)); + } + + @Test public void testUidUpdateChangesInterfaceFilteringRule() throws Exception { LinkProperties lp = new LinkProperties(); lp.setInterfaceName("tun0"); + lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); // The uid range needs to cover the test app so the network is visible to it. final UidRange vpnRange = UidRange.createForUser(VPN_USER); - final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, VPN_UID, - Collections.singleton(vpnRange)); + mMockVpn.establish(lp, VPN_UID, Collections.singleton(vpnRange)); reset(mMockNetd); InOrder inOrder = inOrder(mMockNetd); @@ -6508,7 +7103,7 @@ public class ConnectivityServiceTest { final Set<UidRange> newRanges = new HashSet<>(Arrays.asList( new UidRange(vpnRange.start, APP1_UID - 1), new UidRange(APP1_UID + 1, vpnRange.stop))); - vpnNetworkAgent.setUids(newRanges); + mMockVpn.setUids(newRanges); waitForIdle(); ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class); @@ -6519,25 +7114,215 @@ public class ConnectivityServiceTest { assertContainsExactly(uidCaptor.getValue(), APP2_UID); } + @Test + public void testLinkPropertiesWithWakeOnLanForActiveNetwork() throws Exception { + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + + LinkProperties wifiLp = new LinkProperties(); + wifiLp.setInterfaceName(WIFI_WOL_IFNAME); + wifiLp.setWakeOnLanSupported(false); - private MockNetworkAgent establishVpn(LinkProperties lp, int establishingUid, - Set<UidRange> vpnRange) { - final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN, lp); - vpnNetworkAgent.getNetworkCapabilities().setEstablishingVpnAppUid(establishingUid); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.connect(); - mMockVpn.setUids(vpnRange); - vpnNetworkAgent.connect(true); + // Default network switch should update ifaces. + mWiFiNetworkAgent.connect(false); + mWiFiNetworkAgent.sendLinkProperties(wifiLp); waitForIdle(); - return vpnNetworkAgent; + + // ConnectivityService should have changed the WakeOnLanSupported to true + wifiLp.setWakeOnLanSupported(true); + assertEquals(wifiLp, mService.getActiveLinkProperties()); + } + + @Test + public void testLegacyExtraInfoSentToNetworkMonitor() throws Exception { + class TestNetworkAgent extends NetworkAgent { + TestNetworkAgent(Context context, Looper looper, NetworkAgentConfig config) { + super(context, looper, "MockAgent", new NetworkCapabilities(), + new LinkProperties(), 40 , config, null /* provider */); + } + } + final NetworkAgent naNoExtraInfo = new TestNetworkAgent( + mServiceContext, mCsHandlerThread.getLooper(), new NetworkAgentConfig()); + naNoExtraInfo.register(); + verify(mNetworkStack).makeNetworkMonitor(any(), isNull(String.class), any()); + naNoExtraInfo.unregister(); + + reset(mNetworkStack); + final NetworkAgentConfig config = + new NetworkAgentConfig.Builder().setLegacyExtraInfo("legacyinfo").build(); + final NetworkAgent naExtraInfo = new TestNetworkAgent( + mServiceContext, mCsHandlerThread.getLooper(), config); + naExtraInfo.register(); + verify(mNetworkStack).makeNetworkMonitor(any(), eq("legacyinfo"), any()); + naExtraInfo.unregister(); + } + + private void setupLocationPermissions( + int targetSdk, boolean locationToggle, String op, String perm) throws Exception { + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.targetSdkVersion = targetSdk; + when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), any())) + .thenReturn(applicationInfo); + + when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(locationToggle); + + if (op != null) { + when(mAppOpsManager.noteOp(eq(op), eq(Process.myUid()), eq(mContext.getPackageName()))) + .thenReturn(AppOpsManager.MODE_ALLOWED); + } + + if (perm != null) { + mServiceContext.setPermission(perm, PERMISSION_GRANTED); + } + } + + private int getOwnerUidNetCapsForCallerPermission(int ownerUid, int callerUid) { + final NetworkCapabilities netCap = new NetworkCapabilities().setOwnerUid(ownerUid); + + return mService + .maybeSanitizeLocationInfoForCaller(netCap, callerUid, mContext.getPackageName()) + .getOwnerUid(); + } + + @Test + public void testMaybeSanitizeLocationInfoForCallerWithFineLocationAfterQ() throws Exception { + setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + + final int myUid = Process.myUid(); + assertEquals(myUid, getOwnerUidNetCapsForCallerPermission(myUid, myUid)); + } + + @Test + public void testMaybeSanitizeLocationInfoForCallerWithCoarseLocationPreQ() throws Exception { + setupLocationPermissions(Build.VERSION_CODES.P, true, AppOpsManager.OPSTR_COARSE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION); + + final int myUid = Process.myUid(); + assertEquals(myUid, getOwnerUidNetCapsForCallerPermission(myUid, myUid)); + } + + @Test + public void testMaybeSanitizeLocationInfoForCallerLocationOff() throws Exception { + // Test that even with fine location permission, and UIDs matching, the UID is sanitized. + setupLocationPermissions(Build.VERSION_CODES.Q, false, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + + final int myUid = Process.myUid(); + assertEquals(Process.INVALID_UID, getOwnerUidNetCapsForCallerPermission(myUid, myUid)); + } + + @Test + public void testMaybeSanitizeLocationInfoForCallerWrongUid() throws Exception { + // Test that even with fine location permission, not being the owner leads to sanitization. + setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + + final int myUid = Process.myUid(); + assertEquals(Process.INVALID_UID, getOwnerUidNetCapsForCallerPermission(myUid + 1, myUid)); + } + + @Test + public void testMaybeSanitizeLocationInfoForCallerWithCoarseLocationAfterQ() throws Exception { + // Test that not having fine location permission leads to sanitization. + setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_COARSE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION); + + // Test that without the location permission, the owner field is sanitized. + final int myUid = Process.myUid(); + assertEquals(Process.INVALID_UID, getOwnerUidNetCapsForCallerPermission(myUid, myUid)); + } + + @Test + public void testMaybeSanitizeLocationInfoForCallerWithoutLocationPermission() throws Exception { + setupLocationPermissions(Build.VERSION_CODES.Q, true, null /* op */, null /* perm */); + + // Test that without the location permission, the owner field is sanitized. + final int myUid = Process.myUid(); + assertEquals(Process.INVALID_UID, getOwnerUidNetCapsForCallerPermission(myUid, myUid)); + } + + private void setupConnectionOwnerUid(int vpnOwnerUid, @VpnManager.VpnType int vpnType) + throws Exception { + final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER)); + mMockVpn.establish(new LinkProperties(), vpnOwnerUid, vpnRange); + mMockVpn.setVpnType(vpnType); + + final VpnInfo vpnInfo = new VpnInfo(); + vpnInfo.ownerUid = vpnOwnerUid; + mMockVpn.setVpnInfo(vpnInfo); + } + + private void setupConnectionOwnerUidAsVpnApp(int vpnOwnerUid, @VpnManager.VpnType int vpnType) + throws Exception { + setupConnectionOwnerUid(vpnOwnerUid, vpnType); + + // Test as VPN app + mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); + mServiceContext.setPermission( + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_DENIED); } - private void assertContainsExactly(int[] actual, int... expected) { - int[] sortedActual = Arrays.copyOf(actual, actual.length); - int[] sortedExpected = Arrays.copyOf(expected, expected.length); - Arrays.sort(sortedActual); - Arrays.sort(sortedExpected); - assertArrayEquals(sortedExpected, sortedActual); + private ConnectionInfo getTestConnectionInfo() throws Exception { + return new ConnectionInfo( + IPPROTO_TCP, + new InetSocketAddress(InetAddresses.parseNumericAddress("1.2.3.4"), 1234), + new InetSocketAddress(InetAddresses.parseNumericAddress("2.3.4.5"), 2345)); + } + + @Test + public void testGetConnectionOwnerUidPlatformVpn() throws Exception { + final int myUid = Process.myUid(); + setupConnectionOwnerUidAsVpnApp(myUid, VpnManager.TYPE_VPN_PLATFORM); + + try { + mService.getConnectionOwnerUid(getTestConnectionInfo()); + fail("Expected SecurityException for non-VpnService app"); + } catch (SecurityException expected) { + } + } + + @Test + public void testGetConnectionOwnerUidVpnServiceWrongUser() throws Exception { + final int myUid = Process.myUid(); + setupConnectionOwnerUidAsVpnApp(myUid + 1, VpnManager.TYPE_VPN_SERVICE); + + try { + mService.getConnectionOwnerUid(getTestConnectionInfo()); + fail("Expected SecurityException for non-VpnService app"); + } catch (SecurityException expected) { + } + } + + @Test + public void testGetConnectionOwnerUidVpnServiceDoesNotThrow() throws Exception { + final int myUid = Process.myUid(); + setupConnectionOwnerUidAsVpnApp(myUid, VpnManager.TYPE_VPN_SERVICE); + + // TODO: Test the returned UID + mService.getConnectionOwnerUid(getTestConnectionInfo()); + } + + @Test + public void testGetConnectionOwnerUidVpnServiceNetworkStackDoesNotThrow() throws Exception { + final int myUid = Process.myUid(); + setupConnectionOwnerUid(myUid, VpnManager.TYPE_VPN_SERVICE); + mServiceContext.setPermission( + android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED); + + // TODO: Test the returned UID + mService.getConnectionOwnerUid(getTestConnectionInfo()); + } + + @Test + public void testGetConnectionOwnerUidVpnServiceMainlineNetworkStackDoesNotThrow() + throws Exception { + final int myUid = Process.myUid(); + setupConnectionOwnerUid(myUid, VpnManager.TYPE_VPN_SERVICE); + mServiceContext.setPermission( + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_GRANTED); + + // TODO: Test the returned UID + mService.getConnectionOwnerUid(getTestConnectionInfo()); } private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid) { @@ -6556,4 +7341,411 @@ public class ConnectivityServiceTest { UserHandle.getAppId(uid)); return packageInfo; } + + @Test + public void testRegisterConnectivityDiagnosticsCallbackInvalidRequest() throws Exception { + final NetworkRequest request = + new NetworkRequest( + new NetworkCapabilities(), TYPE_ETHERNET, 0, NetworkRequest.Type.NONE); + try { + mService.registerConnectivityDiagnosticsCallback( + mConnectivityDiagnosticsCallback, request, mContext.getPackageName()); + fail("registerConnectivityDiagnosticsCallback should throw on invalid NetworkRequest"); + } catch (IllegalArgumentException expected) { + } + } + + private void assertRouteInfoParcelMatches(RouteInfo route, RouteInfoParcel parcel) { + assertEquals(route.getDestination().toString(), parcel.destination); + assertEquals(route.getInterface(), parcel.ifName); + assertEquals(route.getMtu(), parcel.mtu); + + switch (route.getType()) { + case RouteInfo.RTN_UNICAST: + if (route.hasGateway()) { + assertEquals(route.getGateway().getHostAddress(), parcel.nextHop); + } else { + assertEquals(INetd.NEXTHOP_NONE, parcel.nextHop); + } + break; + case RouteInfo.RTN_UNREACHABLE: + assertEquals(INetd.NEXTHOP_UNREACHABLE, parcel.nextHop); + break; + case RouteInfo.RTN_THROW: + assertEquals(INetd.NEXTHOP_THROW, parcel.nextHop); + break; + default: + assertEquals(INetd.NEXTHOP_NONE, parcel.nextHop); + break; + } + } + + private void assertRoutesAdded(int netId, RouteInfo... routes) throws Exception { + ArgumentCaptor<RouteInfoParcel> captor = ArgumentCaptor.forClass(RouteInfoParcel.class); + verify(mMockNetd, times(routes.length)).networkAddRouteParcel(eq(netId), captor.capture()); + for (int i = 0; i < routes.length; i++) { + assertRouteInfoParcelMatches(routes[i], captor.getAllValues().get(i)); + } + } + + private void assertRoutesRemoved(int netId, RouteInfo... routes) throws Exception { + ArgumentCaptor<RouteInfoParcel> captor = ArgumentCaptor.forClass(RouteInfoParcel.class); + verify(mMockNetd, times(routes.length)).networkRemoveRouteParcel(eq(netId), + captor.capture()); + for (int i = 0; i < routes.length; i++) { + assertRouteInfoParcelMatches(routes[i], captor.getAllValues().get(i)); + } + } + + @Test + public void testRegisterUnregisterConnectivityDiagnosticsCallback() throws Exception { + final NetworkRequest wifiRequest = + new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build(); + when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); + + mService.registerConnectivityDiagnosticsCallback( + mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName()); + + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + verify(mIBinder).linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); + verify(mConnectivityDiagnosticsCallback).asBinder(); + assertTrue(mService.mConnectivityDiagnosticsCallbacks.containsKey(mIBinder)); + + mService.unregisterConnectivityDiagnosticsCallback(mConnectivityDiagnosticsCallback); + verify(mIBinder, timeout(TIMEOUT_MS)) + .unlinkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); + assertFalse(mService.mConnectivityDiagnosticsCallbacks.containsKey(mIBinder)); + verify(mConnectivityDiagnosticsCallback, atLeastOnce()).asBinder(); + } + + @Test + public void testRegisterDuplicateConnectivityDiagnosticsCallback() throws Exception { + final NetworkRequest wifiRequest = + new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build(); + when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); + + mService.registerConnectivityDiagnosticsCallback( + mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName()); + + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + verify(mIBinder).linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); + verify(mConnectivityDiagnosticsCallback).asBinder(); + assertTrue(mService.mConnectivityDiagnosticsCallbacks.containsKey(mIBinder)); + + // Register the same callback again + mService.registerConnectivityDiagnosticsCallback( + mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName()); + + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + assertTrue(mService.mConnectivityDiagnosticsCallbacks.containsKey(mIBinder)); + } + + @Test + public void testCheckConnectivityDiagnosticsPermissionsNetworkStack() throws Exception { + final NetworkAgentInfo naiWithoutUid = + new NetworkAgentInfo( + null, null, null, null, null, new NetworkCapabilities(), 0, + mServiceContext, null, null, mService, null, null, null, 0, INVALID_UID); + + mServiceContext.setPermission( + android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED); + assertTrue( + "NetworkStack permission not applied", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid(), Process.myUid(), naiWithoutUid, + mContext.getOpPackageName())); + } + + @Test + public void testCheckConnectivityDiagnosticsPermissionsWrongUidPackageName() throws Exception { + final NetworkAgentInfo naiWithoutUid = + new NetworkAgentInfo( + null, null, null, null, null, new NetworkCapabilities(), 0, + mServiceContext, null, null, mService, null, null, null, 0, INVALID_UID); + + mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); + + assertFalse( + "Mismatched uid/package name should not pass the location permission check", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid() + 1, Process.myUid() + 1, naiWithoutUid, + mContext.getOpPackageName())); + } + + @Test + public void testCheckConnectivityDiagnosticsPermissionsNoLocationPermission() throws Exception { + final NetworkAgentInfo naiWithoutUid = + new NetworkAgentInfo( + null, null, null, null, null, new NetworkCapabilities(), 0, + mServiceContext, null, null, mService, null, null, null, 0, INVALID_UID); + + mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); + + assertFalse( + "ACCESS_FINE_LOCATION permission necessary for Connectivity Diagnostics", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid(), Process.myUid(), naiWithoutUid, + mContext.getOpPackageName())); + } + + @Test + public void testCheckConnectivityDiagnosticsPermissionsActiveVpn() throws Exception { + final Network network = new Network(NET_ID); + final NetworkAgentInfo naiWithoutUid = + new NetworkAgentInfo( + null, null, network, null, null, new NetworkCapabilities(), 0, + mServiceContext, null, null, mService, null, null, null, 0, INVALID_UID); + + setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + + // setUp() calls mockVpn() which adds a VPN with the Test Runner's uid. Configure it to be + // active + final VpnInfo info = new VpnInfo(); + info.ownerUid = Process.myUid(); + info.vpnIface = VPN_IFNAME; + mMockVpn.setVpnInfo(info); + + mMockVpn.establishForMyUid(); + waitForIdle(); + + mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); + + + assertTrue(mService.setUnderlyingNetworksForVpn(new Network[] {network})); + assertTrue( + "Active VPN permission not applied", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid(), Process.myUid(), naiWithoutUid, + mContext.getOpPackageName())); + + assertTrue(mService.setUnderlyingNetworksForVpn(null)); + assertFalse( + "VPN shouldn't receive callback on non-underlying network", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid(), Process.myUid(), naiWithoutUid, + mContext.getOpPackageName())); + } + + @Test + public void testCheckConnectivityDiagnosticsPermissionsNetworkAdministrator() throws Exception { + final NetworkCapabilities nc = new NetworkCapabilities(); + nc.setAdministratorUids(new int[] {Process.myUid()}); + final NetworkAgentInfo naiWithUid = + new NetworkAgentInfo( + null, null, null, null, null, nc, 0, mServiceContext, null, null, + mService, null, null, null, 0, INVALID_UID); + + setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); + + assertTrue( + "NetworkCapabilities administrator uid permission not applied", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid(), Process.myUid(), naiWithUid, mContext.getOpPackageName())); + } + + @Test + public void testCheckConnectivityDiagnosticsPermissionsFails() throws Exception { + final NetworkCapabilities nc = new NetworkCapabilities(); + nc.setOwnerUid(Process.myUid()); + nc.setAdministratorUids(new int[] {Process.myUid()}); + final NetworkAgentInfo naiWithUid = + new NetworkAgentInfo( + null, null, null, null, null, nc, 0, mServiceContext, null, null, + mService, null, null, null, 0, INVALID_UID); + + setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); + + // Use wrong pid and uid + assertFalse( + "Permissions allowed when they shouldn't be granted", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid() + 1, Process.myUid() + 1, naiWithUid, + mContext.getOpPackageName())); + } + + @Test + public void testRegisterConnectivityDiagnosticsCallbackCallsOnConnectivityReport() + throws Exception { + // Set up the Network, which leads to a ConnectivityReport being cached for the network. + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(callback); + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setInterfaceName(INTERFACE_NAME); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, linkProperties); + mCellNetworkAgent.connect(true); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + callback.assertNoCallback(); + + final NetworkRequest request = new NetworkRequest.Builder().build(); + when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); + + mServiceContext.setPermission( + android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED); + + mService.registerConnectivityDiagnosticsCallback( + mConnectivityDiagnosticsCallback, request, mContext.getPackageName()); + + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + verify(mConnectivityDiagnosticsCallback) + .onConnectivityReportAvailable(argThat(report -> { + return INTERFACE_NAME.equals(report.getLinkProperties().getInterfaceName()) + && report.getNetworkCapabilities().hasTransport(TRANSPORT_CELLULAR); + })); + } + + private void setUpConnectivityDiagnosticsCallback() throws Exception { + final NetworkRequest request = new NetworkRequest.Builder().build(); + when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); + + mServiceContext.setPermission( + android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED); + + mService.registerConnectivityDiagnosticsCallback( + mConnectivityDiagnosticsCallback, request, mContext.getPackageName()); + + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + // Connect the cell agent verify that it notifies TestNetworkCallback that it is available + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(callback); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + callback.assertNoCallback(); + } + + @Test + public void testConnectivityDiagnosticsCallbackOnConnectivityReportAvailable() + throws Exception { + setUpConnectivityDiagnosticsCallback(); + + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + // Verify onConnectivityReport fired + verify(mConnectivityDiagnosticsCallback).onConnectivityReportAvailable( + argThat(report -> { + final NetworkCapabilities nc = report.getNetworkCapabilities(); + return nc.getUids() == null + && nc.getAdministratorUids().length == 0 + && nc.getOwnerUid() == Process.INVALID_UID; + })); + } + + @Test + public void testConnectivityDiagnosticsCallbackOnDataStallSuspected() throws Exception { + setUpConnectivityDiagnosticsCallback(); + + // Trigger notifyDataStallSuspected() on the INetworkMonitorCallbacks instance in the + // cellular network agent + mCellNetworkAgent.notifyDataStallSuspected(); + + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + // Verify onDataStallSuspected fired + verify(mConnectivityDiagnosticsCallback).onDataStallSuspected( + argThat(report -> { + final NetworkCapabilities nc = report.getNetworkCapabilities(); + return nc.getUids() == null + && nc.getAdministratorUids().length == 0 + && nc.getOwnerUid() == Process.INVALID_UID; + })); + } + + @Test + public void testConnectivityDiagnosticsCallbackOnConnectivityReported() throws Exception { + setUpConnectivityDiagnosticsCallback(); + + final Network n = mCellNetworkAgent.getNetwork(); + final boolean hasConnectivity = true; + mService.reportNetworkConnectivity(n, hasConnectivity); + + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + // Verify onNetworkConnectivityReported fired + verify(mConnectivityDiagnosticsCallback) + .onNetworkConnectivityReported(eq(n), eq(hasConnectivity)); + + final boolean noConnectivity = false; + mService.reportNetworkConnectivity(n, noConnectivity); + + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + // Wait for onNetworkConnectivityReported to fire + verify(mConnectivityDiagnosticsCallback) + .onNetworkConnectivityReported(eq(n), eq(noConnectivity)); + } + + @Test + public void testRouteAddDeleteUpdate() throws Exception { + final NetworkRequest request = new NetworkRequest.Builder().build(); + final TestNetworkCallback networkCallback = new TestNetworkCallback(); + mCm.registerNetworkCallback(request, networkCallback); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + reset(mMockNetd); + mCellNetworkAgent.connect(false); + networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + final int netId = mCellNetworkAgent.getNetwork().netId; + + final String iface = "rmnet_data0"; + final InetAddress gateway = InetAddress.getByName("fe80::5678"); + RouteInfo direct = RouteInfo.makeHostRoute(gateway, iface); + RouteInfo rio1 = new RouteInfo(new IpPrefix("2001:db8:1::/48"), gateway, iface); + RouteInfo rio2 = new RouteInfo(new IpPrefix("2001:db8:2::/48"), gateway, iface); + RouteInfo defaultRoute = new RouteInfo((IpPrefix) null, gateway, iface); + RouteInfo defaultWithMtu = new RouteInfo(null, gateway, iface, RouteInfo.RTN_UNICAST, + 1280 /* mtu */); + + // Send LinkProperties and check that we ask netd to add routes. + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(iface); + lp.addRoute(direct); + lp.addRoute(rio1); + lp.addRoute(defaultRoute); + mCellNetworkAgent.sendLinkProperties(lp); + networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, x -> x.getRoutes().size() == 3); + + assertRoutesAdded(netId, direct, rio1, defaultRoute); + reset(mMockNetd); + + // Send updated LinkProperties and check that we ask netd to add, remove, update routes. + assertTrue(lp.getRoutes().contains(defaultRoute)); + lp.removeRoute(rio1); + lp.addRoute(rio2); + lp.addRoute(defaultWithMtu); + // Ensure adding the same route with a different MTU replaces the previous route. + assertFalse(lp.getRoutes().contains(defaultRoute)); + assertTrue(lp.getRoutes().contains(defaultWithMtu)); + + mCellNetworkAgent.sendLinkProperties(lp); + networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, + x -> x.getRoutes().contains(rio2)); + + assertRoutesRemoved(netId, rio1); + assertRoutesAdded(netId, rio2); + + ArgumentCaptor<RouteInfoParcel> captor = ArgumentCaptor.forClass(RouteInfoParcel.class); + verify(mMockNetd).networkUpdateRouteParcel(eq(netId), captor.capture()); + assertRouteInfoParcelMatches(defaultWithMtu, captor.getValue()); + + + mCm.unregisterNetworkCallback(networkCallback); + } } diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java index 71b72b84de81..529d03c520ba 100644 --- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java +++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java @@ -46,6 +46,7 @@ import android.net.LinkAddress; import android.net.Network; import android.net.NetworkUtils; import android.os.Binder; +import android.os.INetworkManagementService; import android.os.ParcelFileDescriptor; import android.system.Os; import android.test.mock.MockContext; @@ -135,6 +136,7 @@ public class IpSecServiceParameterizedTest { }; INetd mMockNetd; + INetworkManagementService mNetworkManager; PackageManager mMockPkgMgr; IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig; IpSecService mIpSecService; @@ -160,9 +162,10 @@ public class IpSecServiceParameterizedTest { @Before public void setUp() throws Exception { mMockNetd = mock(INetd.class); + mNetworkManager = mock(INetworkManagementService.class); mMockPkgMgr = mock(PackageManager.class); mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class); - mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig); + mIpSecService = new IpSecService(mMockContext, mNetworkManager, mMockIpSecSrvConfig); // Injecting mock netd when(mMockIpSecSrvConfig.getNetdInstance()).thenReturn(mMockNetd); @@ -544,6 +547,16 @@ public class IpSecServiceParameterizedTest { @Test public void testApplyTransportModeTransform() throws Exception { + verifyApplyTransportModeTransformCommon(false); + } + + @Test + public void testApplyTransportModeTransformReleasedSpi() throws Exception { + verifyApplyTransportModeTransformCommon(true); + } + + public void verifyApplyTransportModeTransformCommon( + boolean closeSpiBeforeApply) throws Exception { IpSecConfig ipSecConfig = new IpSecConfig(); addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); addAuthAndCryptToIpSecConfig(ipSecConfig); @@ -551,6 +564,39 @@ public class IpSecServiceParameterizedTest { IpSecTransformResponse createTransformResp = mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + if (closeSpiBeforeApply) { + mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); + } + + Socket socket = new Socket(); + socket.bind(null); + ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket); + + int resourceId = createTransformResp.resourceId; + mIpSecService.applyTransportModeTransform(pfd, IpSecManager.DIRECTION_OUT, resourceId); + + verify(mMockNetd) + .ipSecApplyTransportModeTransform( + eq(pfd), + eq(mUid), + eq(IpSecManager.DIRECTION_OUT), + anyString(), + anyString(), + eq(TEST_SPI)); + } + + @Test + public void testApplyTransportModeTransformWithClosedSpi() throws Exception { + IpSecConfig ipSecConfig = new IpSecConfig(); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + + // Close SPI record + mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); + Socket socket = new Socket(); socket.bind(null); ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket); @@ -609,6 +655,7 @@ public class IpSecServiceParameterizedTest { anyInt(), anyInt(), anyInt()); + verify(mNetworkManager).setInterfaceUp(createTunnelResp.interfaceName); } @Test @@ -656,6 +703,15 @@ public class IpSecServiceParameterizedTest { @Test public void testApplyTunnelModeTransform() throws Exception { + verifyApplyTunnelModeTransformCommon(false); + } + + @Test + public void testApplyTunnelModeTransformReleasedSpi() throws Exception { + verifyApplyTunnelModeTransformCommon(true); + } + + public void verifyApplyTunnelModeTransformCommon(boolean closeSpiBeforeApply) throws Exception { IpSecConfig ipSecConfig = new IpSecConfig(); ipSecConfig.setMode(IpSecTransform.MODE_TUNNEL); addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); @@ -666,6 +722,49 @@ public class IpSecServiceParameterizedTest { IpSecTunnelInterfaceResponse createTunnelResp = createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage"); + if (closeSpiBeforeApply) { + mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); + } + + int transformResourceId = createTransformResp.resourceId; + int tunnelResourceId = createTunnelResp.resourceId; + mIpSecService.applyTunnelModeTransform(tunnelResourceId, IpSecManager.DIRECTION_OUT, + transformResourceId, "blessedPackage"); + + for (int selAddrFamily : ADDRESS_FAMILIES) { + verify(mMockNetd) + .ipSecUpdateSecurityPolicy( + eq(mUid), + eq(selAddrFamily), + eq(IpSecManager.DIRECTION_OUT), + anyString(), + anyString(), + eq(TEST_SPI), + anyInt(), // iKey/oKey + anyInt(), // mask + eq(tunnelResourceId)); + } + + ipSecConfig.setXfrmInterfaceId(tunnelResourceId); + verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp); + } + + + @Test + public void testApplyTunnelModeTransformWithClosedSpi() throws Exception { + IpSecConfig ipSecConfig = new IpSecConfig(); + ipSecConfig.setMode(IpSecTransform.MODE_TUNNEL); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage"); + + // Close SPI record + mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); + int transformResourceId = createTransformResp.resourceId; int tunnelResourceId = createTunnelResp.resourceId; mIpSecService.applyTunnelModeTransform(tunnelResourceId, IpSecManager.DIRECTION_OUT, diff --git a/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java b/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java index 22a2c94fc194..788e4efe097e 100644 --- a/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java +++ b/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java @@ -31,6 +31,7 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.os.Binder; import android.os.IBinder; +import android.os.INetworkManagementService; import android.os.RemoteException; import androidx.test.filters.SmallTest; @@ -61,7 +62,8 @@ public class IpSecServiceRefcountedResourceTest { public void setUp() throws Exception { mMockContext = mock(Context.class); mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class); - mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig); + mIpSecService = new IpSecService( + mMockContext, mock(INetworkManagementService.class), mMockIpSecSrvConfig); } private void assertResourceState( diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java index 4a35015044ff..536e98327e1f 100644 --- a/tests/net/java/com/android/server/IpSecServiceTest.java +++ b/tests/net/java/com/android/server/IpSecServiceTest.java @@ -42,6 +42,7 @@ import android.net.IpSecManager; import android.net.IpSecSpiResponse; import android.net.IpSecUdpEncapResponse; import android.os.Binder; +import android.os.INetworkManagementService; import android.os.ParcelFileDescriptor; import android.os.Process; import android.system.ErrnoException; @@ -115,6 +116,7 @@ public class IpSecServiceTest { } Context mMockContext; + INetworkManagementService mMockNetworkManager; INetd mMockNetd; IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig; IpSecService mIpSecService; @@ -122,9 +124,10 @@ public class IpSecServiceTest { @Before public void setUp() throws Exception { mMockContext = mock(Context.class); + mMockNetworkManager = mock(INetworkManagementService.class); mMockNetd = mock(INetd.class); mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class); - mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig); + mIpSecService = new IpSecService(mMockContext, mMockNetworkManager, mMockIpSecSrvConfig); // Injecting mock netd when(mMockIpSecSrvConfig.getNetdInstance()).thenReturn(mMockNetd); @@ -132,7 +135,7 @@ public class IpSecServiceTest { @Test public void testIpSecServiceCreate() throws InterruptedException { - IpSecService ipSecSrv = IpSecService.create(mMockContext); + IpSecService ipSecSrv = IpSecService.create(mMockContext, mMockNetworkManager); assertNotNull(ipSecSrv); } @@ -604,8 +607,8 @@ public class IpSecServiceTest { @Test public void testOpenUdpEncapSocketTagsSocket() throws Exception { IpSecService.UidFdTagger mockTagger = mock(IpSecService.UidFdTagger.class); - IpSecService testIpSecService = - new IpSecService(mMockContext, mMockIpSecSrvConfig, mockTagger); + IpSecService testIpSecService = new IpSecService( + mMockContext, mMockNetworkManager, mMockIpSecSrvConfig, mockTagger); IpSecUdpEncapResponse udpEncapResp = testIpSecService.openUdpEncapsulationSocket(0, new Binder()); diff --git a/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt b/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt index f045369459c9..42d4cf3c382b 100644 --- a/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt +++ b/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt @@ -18,6 +18,7 @@ package com.android.server import android.net.ConnectivityManager.TYPE_ETHERNET import android.net.ConnectivityManager.TYPE_MOBILE +import android.net.ConnectivityManager.TYPE_MOBILE_SUPL import android.net.ConnectivityManager.TYPE_WIFI import android.net.ConnectivityManager.TYPE_WIMAX import android.net.NetworkInfo.DetailedState.CONNECTED @@ -46,7 +47,7 @@ const val UNSUPPORTED_TYPE = TYPE_WIMAX @RunWith(AndroidJUnit4::class) @SmallTest class LegacyTypeTrackerTest { - private val supportedTypes = arrayOf(TYPE_MOBILE, TYPE_WIFI, TYPE_ETHERNET) + private val supportedTypes = arrayOf(TYPE_MOBILE, TYPE_WIFI, TYPE_ETHERNET, TYPE_MOBILE_SUPL) private val mMockService = mock(ConnectivityService::class.java).apply { doReturn(false).`when`(this).isDefaultNetwork(any()) @@ -70,6 +71,26 @@ class LegacyTypeTrackerTest { } @Test + fun testSupl() { + val mobileNai = mock(NetworkAgentInfo::class.java) + mTracker.add(TYPE_MOBILE, mobileNai) + verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE) + reset(mMockService) + mTracker.add(TYPE_MOBILE_SUPL, mobileNai) + verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE_SUPL) + reset(mMockService) + mTracker.remove(TYPE_MOBILE_SUPL, mobileNai, false /* wasDefault */) + verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE_SUPL) + reset(mMockService) + mTracker.add(TYPE_MOBILE_SUPL, mobileNai) + verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE_SUPL) + reset(mMockService) + mTracker.remove(mobileNai, false) + verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE_SUPL) + verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE) + } + + @Test fun testAddNetwork() { val mobileNai = mock(NetworkAgentInfo::class.java) val wifiNai = mock(NetworkAgentInfo::class.java) diff --git a/tests/net/java/com/android/server/NetIdManagerTest.kt b/tests/net/java/com/android/server/NetIdManagerTest.kt new file mode 100644 index 000000000000..045f89f85e3b --- /dev/null +++ b/tests/net/java/com/android/server/NetIdManagerTest.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server + +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.server.NetIdManager.MIN_NET_ID +import com.android.testutils.ExceptionUtils.ThrowingRunnable +import com.android.testutils.assertThrows +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.assertEquals + +@RunWith(AndroidJUnit4::class) +@SmallTest +class NetIdManagerTest { + @Test + fun testReserveReleaseNetId() { + val manager = NetIdManager(MIN_NET_ID + 4) + assertEquals(MIN_NET_ID, manager.reserveNetId()) + assertEquals(MIN_NET_ID + 1, manager.reserveNetId()) + assertEquals(MIN_NET_ID + 2, manager.reserveNetId()) + assertEquals(MIN_NET_ID + 3, manager.reserveNetId()) + + manager.releaseNetId(MIN_NET_ID + 1) + manager.releaseNetId(MIN_NET_ID + 3) + // IDs only loop once there is no higher ID available + assertEquals(MIN_NET_ID + 4, manager.reserveNetId()) + assertEquals(MIN_NET_ID + 1, manager.reserveNetId()) + assertEquals(MIN_NET_ID + 3, manager.reserveNetId()) + assertThrows(IllegalStateException::class.java, ThrowingRunnable { manager.reserveNetId() }) + manager.releaseNetId(MIN_NET_ID + 5) + // Still no ID available: MIN_NET_ID + 5 was not reserved + assertThrows(IllegalStateException::class.java, ThrowingRunnable { manager.reserveNetId() }) + manager.releaseNetId(MIN_NET_ID + 2) + // Throwing an exception still leaves the manager in a working state + assertEquals(MIN_NET_ID + 2, manager.reserveNetId()) + } +}
\ No newline at end of file diff --git a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java index 8fa0ab979a54..508b5cd9cb19 100644 --- a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java +++ b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java @@ -18,35 +18,56 @@ package com.android.server.connectivity; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; +import static android.net.NetworkCapabilities.MAX_TRANSPORT; +import static android.net.NetworkCapabilities.MIN_TRANSPORT; +import static android.net.NetworkCapabilities.TRANSPORT_VPN; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE; import static android.provider.Settings.Global.PRIVATE_DNS_MODE; import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER; +import static com.android.testutils.MiscAssertsKt.assertContainsExactly; +import static com.android.testutils.MiscAssertsKt.assertContainsStringsExactly; +import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.NonNull; import android.content.Context; import android.net.IDnsResolver; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.ResolverOptionsParcel; +import android.net.ResolverParamsParcel; import android.net.RouteInfo; import android.net.shared.PrivateDnsConfig; import android.provider.Settings; import android.test.mock.MockContentResolver; +import android.util.SparseArray; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.util.MessageUtils; import com.android.internal.util.test.FakeSettingsProvider; +import libcore.net.InetAddressUtils; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -66,8 +87,11 @@ public class DnsManagerTest { static final int TEST_NETID = 100; static final int TEST_NETID_ALTERNATE = 101; static final int TEST_NETID_UNTRACKED = 102; - final boolean IS_DEFAULT = true; - final boolean NOT_DEFAULT = false; + static final int TEST_DEFAULT_SAMPLE_VALIDITY_SECONDS = 1800; + static final int TEST_DEFAULT_SUCCESS_THRESHOLD_PERCENT = 25; + static final int TEST_DEFAULT_MIN_SAMPLES = 8; + static final int TEST_DEFAULT_MAX_SAMPLES = 64; + static final int[] TEST_TRANSPORT_TYPES = {TRANSPORT_WIFI, TRANSPORT_VPN}; DnsManager mDnsManager; MockContentResolver mContentResolver; @@ -76,6 +100,36 @@ public class DnsManagerTest { @Mock IDnsResolver mMockDnsResolver; @Mock MockableSystemProperties mSystemProperties; + private void assertResolverOptionsEquals( + @NonNull ResolverOptionsParcel actual, + @NonNull ResolverOptionsParcel expected) { + assertEquals(actual.hosts, expected.hosts); + assertEquals(actual.tcMode, expected.tcMode); + assertEquals(actual.enforceDnsUid, expected.enforceDnsUid); + assertFieldCountEquals(3, ResolverOptionsParcel.class); + } + + private void assertResolverParamsEquals(@NonNull ResolverParamsParcel actual, + @NonNull ResolverParamsParcel expected) { + assertEquals(actual.netId, expected.netId); + assertEquals(actual.sampleValiditySeconds, expected.sampleValiditySeconds); + assertEquals(actual.successThreshold, expected.successThreshold); + assertEquals(actual.minSamples, expected.minSamples); + assertEquals(actual.maxSamples, expected.maxSamples); + assertEquals(actual.baseTimeoutMsec, expected.baseTimeoutMsec); + assertEquals(actual.retryCount, expected.retryCount); + assertContainsStringsExactly(actual.servers, expected.servers); + assertContainsStringsExactly(actual.domains, expected.domains); + assertEquals(actual.tlsName, expected.tlsName); + assertContainsStringsExactly(actual.tlsServers, expected.tlsServers); + assertContainsStringsExactly(actual.tlsFingerprints, expected.tlsFingerprints); + assertEquals(actual.caCertificate, expected.caCertificate); + assertEquals(actual.tlsConnectTimeoutMs, expected.tlsConnectTimeoutMs); + assertResolverOptionsEquals(actual.resolverOptions, expected.resolverOptions); + assertContainsExactly(actual.transportTypes, expected.transportTypes); + assertFieldCountEquals(16, ResolverParamsParcel.class); + } + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -103,8 +157,13 @@ public class DnsManagerTest { lp.addDnsServer(InetAddress.getByName("4.4.4.4")); // Send a validation event that is tracked on the alternate netId - mDnsManager.setDnsConfigurationForNetwork(TEST_NETID, lp, IS_DEFAULT); - mDnsManager.setDnsConfigurationForNetwork(TEST_NETID_ALTERNATE, lp, NOT_DEFAULT); + mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES); + mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp); + mDnsManager.setDefaultDnsSystemProperties(lp.getDnsServers()); + mDnsManager.flushVmDnsCache(); + mDnsManager.updateTransportsForNetwork(TEST_NETID_ALTERNATE, TEST_TRANSPORT_TYPES); + mDnsManager.noteDnsServersForNetwork(TEST_NETID_ALTERNATE, lp); + mDnsManager.flushVmDnsCache(); mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID_ALTERNATE, InetAddress.parseNumericAddress("4.4.4.4"), "", true)); @@ -135,7 +194,10 @@ public class DnsManagerTest { InetAddress.parseNumericAddress("6.6.6.6"), InetAddress.parseNumericAddress("2001:db8:66:66::1") })); - mDnsManager.setDnsConfigurationForNetwork(TEST_NETID, lp, IS_DEFAULT); + mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES); + mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp); + mDnsManager.setDefaultDnsSystemProperties(lp.getDnsServers()); + mDnsManager.flushVmDnsCache(); fixedLp = new LinkProperties(lp); mDnsManager.updatePrivateDnsStatus(TEST_NETID, fixedLp); assertTrue(fixedLp.isPrivateDnsActive()); @@ -168,7 +230,10 @@ public class DnsManagerTest { // be tracked. LinkProperties lp = new LinkProperties(); lp.addDnsServer(InetAddress.getByName("3.3.3.3")); - mDnsManager.setDnsConfigurationForNetwork(TEST_NETID, lp, IS_DEFAULT); + mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES); + mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp); + mDnsManager.setDefaultDnsSystemProperties(lp.getDnsServers()); + mDnsManager.flushVmDnsCache(); mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, InetAddress.parseNumericAddress("3.3.3.3"), "", true)); @@ -179,7 +244,10 @@ public class DnsManagerTest { // Validation event has untracked netId mDnsManager.updatePrivateDns(new Network(TEST_NETID), mDnsManager.getPrivateDnsConfig()); - mDnsManager.setDnsConfigurationForNetwork(TEST_NETID, lp, IS_DEFAULT); + mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES); + mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp); + mDnsManager.setDefaultDnsSystemProperties(lp.getDnsServers()); + mDnsManager.flushVmDnsCache(); mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID_UNTRACKED, InetAddress.parseNumericAddress("3.3.3.3"), "", true)); @@ -225,7 +293,10 @@ public class DnsManagerTest { Settings.Global.putString(mContentResolver, PRIVATE_DNS_MODE, PRIVATE_DNS_MODE_OFF); mDnsManager.updatePrivateDns(new Network(TEST_NETID), mDnsManager.getPrivateDnsConfig()); - mDnsManager.setDnsConfigurationForNetwork(TEST_NETID, lp, IS_DEFAULT); + mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES); + mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp); + mDnsManager.setDefaultDnsSystemProperties(lp.getDnsServers()); + mDnsManager.flushVmDnsCache(); mDnsManager.updatePrivateDnsValidation( new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, InetAddress.parseNumericAddress("3.3.3.3"), "", true)); @@ -258,4 +329,102 @@ public class DnsManagerTest { assertEquals("strictmode.com", cfgStrict.hostname); assertEquals(new InetAddress[0], cfgStrict.ips); } + + @Test + public void testSendDnsConfiguration() throws Exception { + reset(mMockDnsResolver); + mDnsManager.updatePrivateDns(new Network(TEST_NETID), + mDnsManager.getPrivateDnsConfig()); + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(TEST_IFACENAME); + lp.addDnsServer(InetAddress.getByName("3.3.3.3")); + lp.addDnsServer(InetAddress.getByName("4.4.4.4")); + mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES); + mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp); + mDnsManager.setDefaultDnsSystemProperties(lp.getDnsServers()); + mDnsManager.flushVmDnsCache(); + + final ArgumentCaptor<ResolverParamsParcel> resolverParamsParcelCaptor = + ArgumentCaptor.forClass(ResolverParamsParcel.class); + verify(mMockDnsResolver, times(1)).setResolverConfiguration( + resolverParamsParcelCaptor.capture()); + final ResolverParamsParcel actualParams = resolverParamsParcelCaptor.getValue(); + final ResolverParamsParcel expectedParams = new ResolverParamsParcel(); + expectedParams.netId = TEST_NETID; + expectedParams.sampleValiditySeconds = TEST_DEFAULT_SAMPLE_VALIDITY_SECONDS; + expectedParams.successThreshold = TEST_DEFAULT_SUCCESS_THRESHOLD_PERCENT; + expectedParams.minSamples = TEST_DEFAULT_MIN_SAMPLES; + expectedParams.maxSamples = TEST_DEFAULT_MAX_SAMPLES; + expectedParams.servers = new String[]{"3.3.3.3", "4.4.4.4"}; + expectedParams.domains = new String[]{}; + expectedParams.tlsName = ""; + expectedParams.tlsServers = new String[]{"3.3.3.3", "4.4.4.4"}; + expectedParams.transportTypes = TEST_TRANSPORT_TYPES; + expectedParams.resolverOptions = new ResolverOptionsParcel(); + assertResolverParamsEquals(actualParams, expectedParams); + } + + @Test + public void testTransportTypesEqual() throws Exception { + SparseArray<String> ncTransTypes = MessageUtils.findMessageNames( + new Class[] { NetworkCapabilities.class }, new String[]{ "TRANSPORT_" }); + SparseArray<String> dnsTransTypes = MessageUtils.findMessageNames( + new Class[] { IDnsResolver.class }, new String[]{ "TRANSPORT_" }); + assertEquals(0, MIN_TRANSPORT); + assertEquals(MAX_TRANSPORT + 1, ncTransTypes.size()); + // TRANSPORT_UNKNOWN in IDnsResolver is defined to -1 and only for resolver. + assertEquals("TRANSPORT_UNKNOWN", dnsTransTypes.get(-1)); + assertEquals(ncTransTypes.size(), dnsTransTypes.size() - 1); + for (int i = MIN_TRANSPORT; i < MAX_TRANSPORT; i++) { + String name = ncTransTypes.get(i, null); + assertNotNull("Could not find NetworkCapabilies.TRANSPORT_* constant equal to " + + i, name); + assertEquals(name, dnsTransTypes.get(i)); + } + } + + @Test + public void testGetPrivateDnsConfigForNetwork() throws Exception { + final Network network = new Network(TEST_NETID); + final InetAddress dnsAddr = InetAddressUtils.parseNumericAddress("3.3.3.3"); + final InetAddress[] tlsAddrs = new InetAddress[]{ + InetAddressUtils.parseNumericAddress("6.6.6.6"), + InetAddressUtils.parseNumericAddress("2001:db8:66:66::1") + }; + final String tlsName = "strictmode.com"; + LinkProperties lp = new LinkProperties(); + lp.addDnsServer(dnsAddr); + + // The PrivateDnsConfig map is empty, so the default PRIVATE_DNS_OFF is returned. + PrivateDnsConfig privateDnsCfg = mDnsManager.getPrivateDnsConfig(network); + assertFalse(privateDnsCfg.useTls); + assertEquals("", privateDnsCfg.hostname); + assertEquals(new InetAddress[0], privateDnsCfg.ips); + + // An entry with default PrivateDnsConfig is added to the PrivateDnsConfig map. + mDnsManager.updatePrivateDns(network, mDnsManager.getPrivateDnsConfig()); + mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp); + mDnsManager.updatePrivateDnsValidation( + new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, dnsAddr, "", true)); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); + privateDnsCfg = mDnsManager.getPrivateDnsConfig(network); + assertTrue(privateDnsCfg.useTls); + assertEquals("", privateDnsCfg.hostname); + assertEquals(new InetAddress[0], privateDnsCfg.ips); + + // The original entry is overwritten by a new PrivateDnsConfig. + mDnsManager.updatePrivateDns(network, new PrivateDnsConfig(tlsName, tlsAddrs)); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); + privateDnsCfg = mDnsManager.getPrivateDnsConfig(network); + assertTrue(privateDnsCfg.useTls); + assertEquals(tlsName, privateDnsCfg.hostname); + assertEquals(tlsAddrs, privateDnsCfg.ips); + + // The network is removed, so the PrivateDnsConfig map becomes empty again. + mDnsManager.removeNetwork(network); + privateDnsCfg = mDnsManager.getPrivateDnsConfig(network); + assertFalse(privateDnsCfg.useTls); + assertEquals("", privateDnsCfg.hostname); + assertEquals(new InetAddress[0], privateDnsCfg.ips); + } } diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java index 142769f61335..aafa18a532fa 100644 --- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java @@ -36,9 +36,9 @@ import android.net.IDnsResolver; import android.net.INetd; import android.net.Network; import android.net.NetworkCapabilities; -import android.net.NetworkFactory; import android.net.NetworkInfo; -import android.net.NetworkMisc; +import android.net.NetworkProvider; +import android.os.Binder; import android.os.INetworkManagementService; import android.text.format.DateUtils; @@ -74,7 +74,6 @@ public class LingerMonitorTest { @Mock INetd mNetd; @Mock INetworkManagementService mNMS; @Mock Context mCtx; - @Mock NetworkMisc mMisc; @Mock NetworkNotificationManager mNotifier; @Mock Resources mResources; @@ -355,8 +354,8 @@ public class LingerMonitorTest { caps.addCapability(0); caps.addTransportType(transport); NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null, - caps, 50, mCtx, null, mMisc, mConnService, mNetd, mDnsResolver, mNMS, - NetworkFactory.SerialNumber.NONE); + caps, 50, mCtx, null, null /* config */, mConnService, mNetd, mDnsResolver, mNMS, + NetworkProvider.ID_NONE, Binder.getCallingUid()); nai.everValidated = true; return nai; } diff --git a/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java b/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java index b783467cfaf2..de1028cd2303 100644 --- a/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java +++ b/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java @@ -51,6 +51,7 @@ import android.net.NetworkPolicy; import android.net.NetworkPolicyManager; import android.net.NetworkTemplate; import android.net.StringNetworkSpecifier; +import android.net.TelephonyNetworkSpecifier; import android.os.Handler; import android.provider.Settings; import android.telephony.TelephonyManager; @@ -229,7 +230,7 @@ public class MultipathPolicyTrackerTest { verify(mCM).registerNetworkCallback(any(), networkCallback.capture(), any()); // Simulate callback after capability changes - final NetworkCapabilities capabilities = new NetworkCapabilities() + NetworkCapabilities capabilities = new NetworkCapabilities() .addCapability(NET_CAPABILITY_INTERNET) .addTransportType(TRANSPORT_CELLULAR) .setNetworkSpecifier(new StringNetworkSpecifier("234")); @@ -239,6 +240,19 @@ public class MultipathPolicyTrackerTest { networkCallback.getValue().onCapabilitiesChanged( TEST_NETWORK, capabilities); + + // make sure it also works with the new introduced TelephonyNetworkSpecifier + capabilities = new NetworkCapabilities() + .addCapability(NET_CAPABILITY_INTERNET) + .addTransportType(TRANSPORT_CELLULAR) + .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder() + .setSubscriptionId(234).build()); + if (!roaming) { + capabilities.addCapability(NET_CAPABILITY_NOT_ROAMING); + } + networkCallback.getValue().onCapabilitiesChanged( + TEST_NETWORK, + capabilities); } @Test diff --git a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java index b709af1a02f1..5046b6586fb0 100644 --- a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java +++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java @@ -33,8 +33,8 @@ import android.net.InterfaceConfiguration; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; +import android.net.NetworkAgentConfig; import android.net.NetworkInfo; -import android.net.NetworkMisc; import android.os.Handler; import android.os.INetworkManagementService; import android.os.test.TestLooper; @@ -58,12 +58,13 @@ public class Nat464XlatTest { static final String BASE_IFACE = "test0"; static final String STACKED_IFACE = "v4-test0"; + static final LinkAddress V6ADDR = new LinkAddress("2001:db8:1::f00/64"); static final LinkAddress ADDR = new LinkAddress("192.0.2.5/29"); static final String NAT64_PREFIX = "64:ff9b::/96"; + static final String OTHER_NAT64_PREFIX = "2001:db8:0:64::/96"; static final int NETID = 42; @Mock ConnectivityService mConnectivity; - @Mock NetworkMisc mMisc; @Mock IDnsResolver mDnsResolver; @Mock INetd mNetd; @Mock INetworkManagementService mNms; @@ -72,6 +73,7 @@ public class Nat464XlatTest { TestLooper mLooper; Handler mHandler; + NetworkAgentConfig mAgentConfig = new NetworkAgentConfig(); Nat464Xlat makeNat464Xlat() { return new Nat464Xlat(mNai, mNetd, mDnsResolver, mNms) { @@ -81,6 +83,14 @@ public class Nat464XlatTest { }; } + private void markNetworkConnected() { + mNai.networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "", ""); + } + + private void markNetworkDisconnected() { + mNai.networkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, "", ""); + } + @Before public void setUp() throws Exception { mLooper = new TestLooper(); @@ -92,8 +102,9 @@ public class Nat464XlatTest { mNai.linkProperties.setInterfaceName(BASE_IFACE); mNai.networkInfo = new NetworkInfo(null); mNai.networkInfo.setType(ConnectivityManager.TYPE_WIFI); + markNetworkConnected(); when(mNai.connService()).thenReturn(mConnectivity); - when(mNai.netMisc()).thenReturn(mMisc); + when(mNai.netAgentConfig()).thenReturn(mAgentConfig); when(mNai.handler()).thenReturn(mHandler); when(mNms.getInterfaceConfig(eq(STACKED_IFACE))).thenReturn(mConfig); @@ -104,7 +115,7 @@ public class Nat464XlatTest { String msg = String.format("requiresClat expected %b for type=%d state=%s skip=%b " + "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(), nai.networkInfo.getDetailedState(), - mMisc.skip464xlat, nai.linkProperties.getNat64Prefix(), + mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(), nai.linkProperties.getLinkAddresses()); assertEquals(msg, expected, Nat464Xlat.requiresClat(nai)); } @@ -113,7 +124,7 @@ public class Nat464XlatTest { String msg = String.format("shouldStartClat expected %b for type=%d state=%s skip=%b " + "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(), nai.networkInfo.getDetailedState(), - mMisc.skip464xlat, nai.linkProperties.getNat64Prefix(), + mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(), nai.linkProperties.getLinkAddresses()); assertEquals(msg, expected, Nat464Xlat.shouldStartClat(nai)); } @@ -139,7 +150,7 @@ public class Nat464XlatTest { for (NetworkInfo.DetailedState state : supportedDetailedStates) { mNai.networkInfo.setDetailedState(state, "reason", "extraInfo"); - mNai.linkProperties.setNat64Prefix(new IpPrefix("2001:db8:0:64::/96")); + mNai.linkProperties.setNat64Prefix(new IpPrefix(OTHER_NAT64_PREFIX)); assertRequiresClat(false, mNai); assertShouldStartClat(false, mNai); @@ -151,11 +162,11 @@ public class Nat464XlatTest { assertRequiresClat(true, mNai); assertShouldStartClat(true, mNai); - mMisc.skip464xlat = true; + mAgentConfig.skip464xlat = true; assertRequiresClat(false, mNai); assertShouldStartClat(false, mNai); - mMisc.skip464xlat = false; + mAgentConfig.skip464xlat = false; assertRequiresClat(true, mNai); assertShouldStartClat(true, mNai); @@ -176,12 +187,21 @@ public class Nat464XlatTest { } } - @Test - public void testNormalStartAndStop() throws Exception { + private void makeClatUnnecessary(boolean dueToDisconnect) { + if (dueToDisconnect) { + markNetworkDisconnected(); + } else { + mNai.linkProperties.addLinkAddress(ADDR); + } + } + + private void checkNormalStartAndStop(boolean dueToDisconnect) throws Exception { Nat464Xlat nat = makeNat464Xlat(); ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class); - nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX)); + mNai.linkProperties.addLinkAddress(V6ADDR); + + nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX)); // Start clat. nat.start(); @@ -200,6 +220,7 @@ public class Nat464XlatTest { assertRunning(nat); // Stop clat (Network disconnects, IPv4 addr appears, ...). + makeClatUnnecessary(dueToDisconnect); nat.stop(); verify(mNetd).clatdStop(eq(BASE_IFACE)); @@ -217,12 +238,24 @@ public class Nat464XlatTest { verifyNoMoreInteractions(mNetd, mNms, mConnectivity); } + @Test + public void testNormalStartAndStopDueToDisconnect() throws Exception { + checkNormalStartAndStop(true); + } + + @Test + public void testNormalStartAndStopDueToIpv4Addr() throws Exception { + checkNormalStartAndStop(false); + } + private void checkStartStopStart(boolean interfaceRemovedFirst) throws Exception { Nat464Xlat nat = makeNat464Xlat(); ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class); InOrder inOrder = inOrder(mNetd, mConnectivity); - nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX)); + mNai.linkProperties.addLinkAddress(V6ADDR); + + nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX)); nat.start(); @@ -309,7 +342,7 @@ public class Nat464XlatTest { Nat464Xlat nat = makeNat464Xlat(); ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class); - nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX)); + nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX)); nat.start(); @@ -344,11 +377,12 @@ public class Nat464XlatTest { verifyNoMoreInteractions(mNetd, mNms, mConnectivity); } - @Test - public void testStopBeforeClatdStarts() throws Exception { + private void checkStopBeforeClatdStarts(boolean dueToDisconnect) throws Exception { Nat464Xlat nat = makeNat464Xlat(); - nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX)); + mNai.linkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64")); + + nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX)); nat.start(); @@ -356,6 +390,7 @@ public class Nat464XlatTest { verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...) + makeClatUnnecessary(dueToDisconnect); nat.stop(); verify(mNetd).clatdStop(eq(BASE_IFACE)); @@ -377,10 +412,21 @@ public class Nat464XlatTest { } @Test - public void testStopAndClatdNeverStarts() throws Exception { + public void testStopDueToDisconnectBeforeClatdStarts() throws Exception { + checkStopBeforeClatdStarts(true); + } + + @Test + public void testStopDueToIpv4AddrBeforeClatdStarts() throws Exception { + checkStopBeforeClatdStarts(false); + } + + private void checkStopAndClatdNeverStarts(boolean dueToDisconnect) throws Exception { Nat464Xlat nat = makeNat464Xlat(); - nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX)); + mNai.linkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64")); + + nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX)); nat.start(); @@ -388,6 +434,7 @@ public class Nat464XlatTest { verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...) + makeClatUnnecessary(dueToDisconnect); nat.stop(); verify(mNetd).clatdStop(eq(BASE_IFACE)); @@ -398,6 +445,57 @@ public class Nat464XlatTest { verifyNoMoreInteractions(mNetd, mNms, mConnectivity); } + @Test + public void testStopDueToDisconnectAndClatdNeverStarts() throws Exception { + checkStopAndClatdNeverStarts(true); + } + + @Test + public void testStopDueToIpv4AddressAndClatdNeverStarts() throws Exception { + checkStopAndClatdNeverStarts(false); + } + + @Test + public void testNat64PrefixPreference() throws Exception { + final IpPrefix prefixFromDns = new IpPrefix(NAT64_PREFIX); + final IpPrefix prefixFromRa = new IpPrefix(OTHER_NAT64_PREFIX); + + Nat464Xlat nat = makeNat464Xlat(); + + final LinkProperties emptyLp = new LinkProperties(); + LinkProperties fixedupLp; + + fixedupLp = new LinkProperties(); + nat.setNat64PrefixFromDns(prefixFromDns); + nat.fixupLinkProperties(emptyLp, fixedupLp); + assertEquals(prefixFromDns, fixedupLp.getNat64Prefix()); + + fixedupLp = new LinkProperties(); + nat.setNat64PrefixFromRa(prefixFromRa); + nat.fixupLinkProperties(emptyLp, fixedupLp); + assertEquals(prefixFromRa, fixedupLp.getNat64Prefix()); + + fixedupLp = new LinkProperties(); + nat.setNat64PrefixFromRa(null); + nat.fixupLinkProperties(emptyLp, fixedupLp); + assertEquals(prefixFromDns, fixedupLp.getNat64Prefix()); + + fixedupLp = new LinkProperties(); + nat.setNat64PrefixFromRa(prefixFromRa); + nat.fixupLinkProperties(emptyLp, fixedupLp); + assertEquals(prefixFromRa, fixedupLp.getNat64Prefix()); + + fixedupLp = new LinkProperties(); + nat.setNat64PrefixFromDns(null); + nat.fixupLinkProperties(emptyLp, fixedupLp); + assertEquals(prefixFromRa, fixedupLp.getNat64Prefix()); + + fixedupLp = new LinkProperties(); + nat.setNat64PrefixFromRa(null); + nat.fixupLinkProperties(emptyLp, fixedupLp); + assertEquals(null, fixedupLp.getNat64Prefix()); + } + static void assertIdle(Nat464Xlat nat) { assertTrue("Nat464Xlat was not IDLE", !nat.isStarted()); } diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java index e4117b848a35..aef9386755d7 100644 --- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java +++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java @@ -19,8 +19,9 @@ package com.android.server.connectivity; import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO; import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME; +import static com.android.testutils.MiscAssertsKt.assertStringContains; + import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -111,15 +112,15 @@ public class NetdEventListenerServiceTest { String[] events2 = remove(listNetdEvent(), baseline); int expectedLength2 = uids.length + 1; // +1 for the WakeupStats line assertEquals(expectedLength2, events2.length); - assertContains(events2[0], "WakeupStats"); - assertContains(events2[0], "wlan0"); - assertContains(events2[0], "0x800"); - assertContains(events2[0], "0x86dd"); + assertStringContains(events2[0], "WakeupStats"); + assertStringContains(events2[0], "wlan0"); + assertStringContains(events2[0], "0x800"); + assertStringContains(events2[0], "0x86dd"); for (int i = 0; i < uids.length; i++) { String got = events2[i+1]; - assertContains(got, "WakeupEvent"); - assertContains(got, "wlan0"); - assertContains(got, "uid: " + uids[i]); + assertStringContains(got, "WakeupEvent"); + assertStringContains(got, "wlan0"); + assertStringContains(got, "uid: " + uids[i]); } int uid = 20000; @@ -131,13 +132,13 @@ public class NetdEventListenerServiceTest { String[] events3 = remove(listNetdEvent(), baseline); int expectedLength3 = BUFFER_LENGTH + 1; // +1 for the WakeupStats line assertEquals(expectedLength3, events3.length); - assertContains(events2[0], "WakeupStats"); - assertContains(events2[0], "wlan0"); + assertStringContains(events2[0], "WakeupStats"); + assertStringContains(events2[0], "wlan0"); for (int i = 1; i < expectedLength3; i++) { String got = events3[i]; - assertContains(got, "WakeupEvent"); - assertContains(got, "wlan0"); - assertContains(got, "uid: " + uid); + assertStringContains(got, "WakeupEvent"); + assertStringContains(got, "wlan0"); + assertStringContains(got, "uid: " + uid); } uid = 45678; @@ -145,9 +146,9 @@ public class NetdEventListenerServiceTest { String[] events4 = remove(listNetdEvent(), baseline); String lastEvent = events4[events4.length - 1]; - assertContains(lastEvent, "WakeupEvent"); - assertContains(lastEvent, "wlan0"); - assertContains(lastEvent, "uid: " + uid); + assertStringContains(lastEvent, "WakeupEvent"); + assertStringContains(lastEvent, "wlan0"); + assertStringContains(lastEvent, "uid: " + uid); } @Test @@ -529,10 +530,6 @@ public class NetdEventListenerServiceTest { return buffer.toString().split("\\n"); } - static void assertContains(String got, String want) { - assertTrue(got + " did not contain \"" + want + "\"", got.contains(want)); - } - static <T> T[] remove(T[] array, T[] filtered) { List<T> c = Arrays.asList(filtered); int next = 0; diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java index 95807638f6c4..47db5d431671 100644 --- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java +++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java @@ -20,6 +20,7 @@ import static com.android.server.connectivity.NetworkNotificationManager.Notific import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -40,6 +41,7 @@ import android.telephony.TelephonyManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.R; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; import org.junit.Before; @@ -60,12 +62,19 @@ public class NetworkNotificationManagerTest { static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities(); static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities(); + static final NetworkCapabilities VPN_CAPABILITIES = new NetworkCapabilities(); static { CELL_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); CELL_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); WIFI_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); WIFI_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + + // Set the underyling network to wifi. + VPN_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); + VPN_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_VPN); + VPN_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + VPN_CAPABILITIES.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN); } @Mock Context mCtx; @@ -75,6 +84,7 @@ public class NetworkNotificationManagerTest { @Mock NotificationManager mNotificationManager; @Mock NetworkAgentInfo mWifiNai; @Mock NetworkAgentInfo mCellNai; + @Mock NetworkAgentInfo mVpnNai; @Mock NetworkInfo mNetworkInfo; ArgumentCaptor<Notification> mCaptor; @@ -88,6 +98,9 @@ public class NetworkNotificationManagerTest { mWifiNai.networkInfo = mNetworkInfo; mCellNai.networkCapabilities = CELL_CAPABILITIES; mCellNai.networkInfo = mNetworkInfo; + mVpnNai.networkCapabilities = VPN_CAPABILITIES; + mVpnNai.networkInfo = mNetworkInfo; + doReturn(true).when(mVpnNai).isVPN(); when(mCtx.getResources()).thenReturn(mResources); when(mCtx.getPackageManager()).thenReturn(mPm); when(mCtx.getApplicationInfo()).thenReturn(new ApplicationInfo()); @@ -97,6 +110,35 @@ public class NetworkNotificationManagerTest { mManager = new NetworkNotificationManager(mCtx, mTelephonyManager, mNotificationManager); } + private void verifyTitleByNetwork(final int id, final NetworkAgentInfo nai, final int title) { + final String tag = NetworkNotificationManager.tagFor(id); + mManager.showNotification(id, PRIVATE_DNS_BROKEN, nai, null, null, true); + verify(mNotificationManager, times(1)) + .notifyAsUser(eq(tag), eq(PRIVATE_DNS_BROKEN.eventId), any(), any()); + final int transportType = NetworkNotificationManager.approximateTransportType(nai); + if (transportType == NetworkCapabilities.TRANSPORT_WIFI) { + verify(mResources, times(1)).getString(title, eq(any())); + } else { + verify(mResources, times(1)).getString(title); + } + verify(mResources, times(1)).getString(R.string.private_dns_broken_detailed); + } + + @Test + public void testTitleOfPrivateDnsBroken() { + // Test the title of mobile data. + verifyTitleByNetwork(100, mCellNai, R.string.mobile_no_internet); + reset(mResources); + + // Test the title of wifi. + verifyTitleByNetwork(101, mWifiNai, R.string.wifi_no_internet); + reset(mResources); + + // Test the title of other networks. + verifyTitleByNetwork(102, mVpnNai, R.string.other_networks_no_internet); + reset(mResources); + } + @Test public void testNotificationsShownAndCleared() { final int NETWORK_ID_BASE = 100; @@ -196,20 +238,6 @@ public class NetworkNotificationManagerTest { } @Test - public void testSameLevelNotifications() { - final int id = 101; - final String tag = NetworkNotificationManager.tagFor(id); - - mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false); - verify(mNotificationManager, times(1)) - .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any()); - - mManager.showNotification(id, LOST_INTERNET, mWifiNai, mCellNai, null, false); - verify(mNotificationManager, times(1)) - .notifyAsUser(eq(tag), eq(LOST_INTERNET.eventId), any(), any()); - } - - @Test public void testClearNotificationByType() { final int id = 101; final String tag = NetworkNotificationManager.tagFor(id); @@ -217,31 +245,25 @@ public class NetworkNotificationManagerTest { // clearNotification(int id, NotificationType notifyType) will check if given type is equal // to previous type or not. If they are equal then clear the notification; if they are not // equal then return. - - mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false); + mManager.showNotification(id, NO_INTERNET, mWifiNai, mCellNai, null, false); verify(mNotificationManager, times(1)) - .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any()); + .notifyAsUser(eq(tag), eq(NO_INTERNET.eventId), any(), any()); - // Previous notification is LOGGED_IN and given type is LOGGED_IN too. The notification + // Previous notification is NO_INTERNET and given type is NO_INTERNET too. The notification // should be cleared. - mManager.clearNotification(id, LOGGED_IN); + mManager.clearNotification(id, NO_INTERNET); verify(mNotificationManager, times(1)) - .cancelAsUser(eq(tag), eq(LOGGED_IN.eventId), any()); - - mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false); - verify(mNotificationManager, times(2)) - .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any()); + .cancelAsUser(eq(tag), eq(NO_INTERNET.eventId), any()); - // LOST_INTERNET notification popup after LOGGED_IN notification. - mManager.showNotification(id, LOST_INTERNET, mWifiNai, mCellNai, null, false); + // SIGN_IN is popped-up. + mManager.showNotification(id, SIGN_IN, mWifiNai, mCellNai, null, false); verify(mNotificationManager, times(1)) - .notifyAsUser(eq(tag), eq(LOST_INTERNET.eventId), any(), any()); + .notifyAsUser(eq(tag), eq(SIGN_IN.eventId), any(), any()); - // Previous notification is LOST_INTERNET and given type is LOGGED_IN. The notification - // shouldn't be cleared. - mManager.clearNotification(id, LOGGED_IN); - // LOST_INTERNET shouldn't be cleared. + // The notification type is not matching previous one, PARTIAL_CONNECTIVITY won't be + // cleared. + mManager.clearNotification(id, PARTIAL_CONNECTIVITY); verify(mNotificationManager, never()) - .cancelAsUser(eq(tag), eq(LOST_INTERNET.eventId), any()); + .cancelAsUser(eq(tag), eq(PARTIAL_CONNECTIVITY.eventId), any()); } } diff --git a/tests/net/java/com/android/server/connectivity/NetworkRankerTest.kt b/tests/net/java/com/android/server/connectivity/NetworkRankerTest.kt new file mode 100644 index 000000000000..86c91165f61b --- /dev/null +++ b/tests/net/java/com/android/server/connectivity/NetworkRankerTest.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity + +import android.net.NetworkRequest +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock +import kotlin.test.assertEquals +import kotlin.test.assertNull + +@RunWith(AndroidJUnit4::class) +@SmallTest +class NetworkRankerTest { + private val ranker = NetworkRanker() + + private fun makeNai(satisfy: Boolean, score: Int) = mock(NetworkAgentInfo::class.java).also { + doReturn(satisfy).`when`(it).satisfies(any()) + doReturn(score).`when`(it).currentScore + } + + @Test + fun testGetBestNetwork() { + val scores = listOf(20, 50, 90, 60, 23, 68) + val nais = scores.map { makeNai(true, it) } + val bestNetwork = nais[2] // The one with the top score + val someRequest = mock(NetworkRequest::class.java) + assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais)) + } + + @Test + fun testIgnoreNonSatisfying() { + val nais = listOf(makeNai(true, 20), makeNai(true, 50), makeNai(false, 90), + makeNai(false, 60), makeNai(true, 23), makeNai(false, 68)) + val bestNetwork = nais[1] // Top score that's satisfying + val someRequest = mock(NetworkRequest::class.java) + assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais)) + } + + @Test + fun testNoMatch() { + val nais = listOf(makeNai(false, 20), makeNai(false, 50), makeNai(false, 90)) + val someRequest = mock(NetworkRequest::class.java) + assertNull(ranker.getBestNetwork(someRequest, nais)) + } + + @Test + fun testEmpty() { + val someRequest = mock(NetworkRequest::class.java) + assertNull(ranker.getBestNetwork(someRequest, emptyList())) + } + + // Make sure the ranker is "stable" (as in stable sort), that is, it always returns the FIRST + // network satisfying the request if multiple of them have the same score. + @Test + fun testStable() { + val nais1 = listOf(makeNai(true, 30), makeNai(true, 30), makeNai(true, 30), + makeNai(true, 30), makeNai(true, 30), makeNai(true, 30)) + val someRequest = mock(NetworkRequest::class.java) + assertEquals(nais1[0], ranker.getBestNetwork(someRequest, nais1)) + + val nais2 = listOf(makeNai(true, 30), makeNai(true, 50), makeNai(true, 20), + makeNai(true, 50), makeNai(true, 50), makeNai(true, 40)) + assertEquals(nais2[1], ranker.getBestNetwork(someRequest, nais2)) + } +} diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java index 2e892e53fa6a..39f849c340f7 100644 --- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java @@ -56,7 +56,6 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; -import android.content.pm.PackageList; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.UserInfo; @@ -72,6 +71,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.LocalServices; +import com.android.server.pm.PackageList; import org.junit.Before; import org.junit.Test; @@ -265,6 +265,8 @@ public class PermissionMonitorTest { assertFalse(mPermissionMonitor.hasNetworkPermission(app)); app = systemPackageInfoWithPermissions(CONNECTIVITY_USE_RESTRICTED_NETWORKS); assertFalse(mPermissionMonitor.hasNetworkPermission(app)); + app = systemPackageInfoWithPermissions(CONNECTIVITY_INTERNAL); + assertFalse(mPermissionMonitor.hasNetworkPermission(app)); } @Test @@ -274,7 +276,7 @@ public class PermissionMonitorTest { PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CHANGE_NETWORK_STATE)); assertTrue(hasRestrictedNetworkPermission( PARTITION_SYSTEM, VERSION_P, MOCK_UID1, NETWORK_STACK)); - assertTrue(hasRestrictedNetworkPermission( + assertFalse(hasRestrictedNetworkPermission( PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CONNECTIVITY_INTERNAL)); assertTrue(hasRestrictedNetworkPermission( PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CONNECTIVITY_USE_RESTRICTED_NETWORKS)); @@ -283,7 +285,7 @@ public class PermissionMonitorTest { assertFalse(hasRestrictedNetworkPermission(PARTITION_SYSTEM, VERSION_Q, MOCK_UID1)); assertFalse(hasRestrictedNetworkPermission( - PARTITION_SYSTEM, VERSION_Q, MOCK_UID1, CHANGE_WIFI_STATE)); + PARTITION_SYSTEM, VERSION_Q, MOCK_UID1, CONNECTIVITY_INTERNAL)); } @Test @@ -291,14 +293,14 @@ public class PermissionMonitorTest { doReturn(VERSION_P).when(mPermissionMonitor).getDeviceFirstSdkInt(); assertTrue(hasRestrictedNetworkPermission(PARTITION_SYSTEM, VERSION_P, SYSTEM_UID)); assertTrue(hasRestrictedNetworkPermission( - PARTITION_SYSTEM, VERSION_P, SYSTEM_UID, CHANGE_WIFI_STATE)); + PARTITION_SYSTEM, VERSION_P, SYSTEM_UID, CONNECTIVITY_INTERNAL)); assertTrue(hasRestrictedNetworkPermission( PARTITION_SYSTEM, VERSION_P, SYSTEM_UID, CONNECTIVITY_USE_RESTRICTED_NETWORKS)); doReturn(VERSION_Q).when(mPermissionMonitor).getDeviceFirstSdkInt(); assertFalse(hasRestrictedNetworkPermission(PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID)); assertFalse(hasRestrictedNetworkPermission( - PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID, CHANGE_WIFI_STATE)); + PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID, CONNECTIVITY_INTERNAL)); assertTrue(hasRestrictedNetworkPermission( PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID, CONNECTIVITY_USE_RESTRICTED_NETWORKS)); } @@ -319,7 +321,7 @@ public class PermissionMonitorTest { assertFalse(hasRestrictedNetworkPermission(PARTITION_VENDOR, VERSION_Q, MOCK_UID1)); assertFalse(hasRestrictedNetworkPermission( - PARTITION_VENDOR, VERSION_Q, MOCK_UID1, CHANGE_WIFI_STATE)); + PARTITION_VENDOR, VERSION_Q, MOCK_UID1, CONNECTIVITY_INTERNAL)); assertFalse(hasRestrictedNetworkPermission( PARTITION_VENDOR, VERSION_Q, MOCK_UID1, CHANGE_NETWORK_STATE)); } @@ -337,7 +339,7 @@ public class PermissionMonitorTest { public void testHasUseBackgroundNetworksPermission() throws Exception { assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(SYSTEM_UID)); assertBackgroundPermission(false, SYSTEM_PACKAGE1, SYSTEM_UID); - assertBackgroundPermission(false, SYSTEM_PACKAGE1, SYSTEM_UID, CHANGE_WIFI_STATE); + assertBackgroundPermission(false, SYSTEM_PACKAGE1, SYSTEM_UID, CONNECTIVITY_INTERNAL); assertBackgroundPermission(true, SYSTEM_PACKAGE1, SYSTEM_UID, CHANGE_NETWORK_STATE); assertBackgroundPermission(true, SYSTEM_PACKAGE1, SYSTEM_UID, NETWORK_STACK); @@ -348,8 +350,9 @@ public class PermissionMonitorTest { assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID2)); assertBackgroundPermission(false, MOCK_PACKAGE2, MOCK_UID2); - assertBackgroundPermission(true, MOCK_PACKAGE2, MOCK_UID2, + assertBackgroundPermission(false, MOCK_PACKAGE2, MOCK_UID2, CONNECTIVITY_INTERNAL); + assertBackgroundPermission(true, MOCK_PACKAGE2, MOCK_UID2, NETWORK_STACK); } private class NetdMonitor { @@ -632,12 +635,18 @@ public class PermissionMonitorTest { mNetdServiceMonitor.expectPermission(INetd.PERMISSION_NONE, new int[]{SYSTEM_UID1}); } - private PackageInfo addPackage(String packageName, int uid, String[] permissions) + private PackageInfo setPackagePermissions(String packageName, int uid, String[] permissions) throws Exception { PackageInfo packageInfo = packageInfoWithPermissions( REQUESTED_PERMISSION_GRANTED, permissions, PARTITION_SYSTEM); when(mPackageManager.getPackageInfo(eq(packageName), anyInt())).thenReturn(packageInfo); when(mPackageManager.getPackagesForUid(eq(uid))).thenReturn(new String[]{packageName}); + return packageInfo; + } + + private PackageInfo addPackage(String packageName, int uid, String[] permissions) + throws Exception { + PackageInfo packageInfo = setPackagePermissions(packageName, uid, permissions); mObserver.onPackageAdded(packageName, uid); return packageInfo; } @@ -688,14 +697,13 @@ public class PermissionMonitorTest { } @Test - public void testPackageUpdate() throws Exception { + public void testPackageRemoveThenAdd() throws Exception { final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService); addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS}); mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1}); - // Remove and install the same package to simulate the update action when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{}); mObserver.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1); mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[]{MOCK_UID1}); @@ -705,6 +713,20 @@ public class PermissionMonitorTest { } @Test + public void testPackageUpdate() throws Exception { + final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService); + + addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {}); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_NONE, new int[]{MOCK_UID1}); + + // When updating a package, the broadcast receiver gets two broadcasts (a remove and then an + // add), but the observer sees only one callback (an update). + setPackagePermissions(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET}); + mObserver.onPackageChanged(MOCK_PACKAGE1, MOCK_UID1); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1}); + } + + @Test public void testPackageUninstallWithMultiplePackages() throws Exception { final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService); diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java deleted file mode 100644 index 8c522f48cfd2..000000000000 --- a/tests/net/java/com/android/server/connectivity/TetheringTest.java +++ /dev/null @@ -1,1022 +0,0 @@ -/* - * 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.connectivity; - -import static android.hardware.usb.UsbManager.USB_CONFIGURED; -import static android.hardware.usb.UsbManager.USB_CONNECTED; -import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS; -import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED; -import static android.net.ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY; -import static android.net.ConnectivityManager.EXTRA_ACTIVE_TETHER; -import static android.net.ConnectivityManager.EXTRA_AVAILABLE_TETHER; -import static android.net.ConnectivityManager.TETHERING_USB; -import static android.net.ConnectivityManager.TETHERING_WIFI; -import static android.net.ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE; -import static android.net.ConnectivityManager.TYPE_MOBILE; -import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; -import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME; -import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE; -import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE; -import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY; -import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED; -import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; -import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER; -import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.notNull; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import android.content.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.res.Resources; -import android.hardware.usb.UsbManager; -import android.net.INetd; -import android.net.INetworkPolicyManager; -import android.net.INetworkStatsService; -import android.net.ITetheringEventCallback; -import android.net.InterfaceConfiguration; -import android.net.IpPrefix; -import android.net.LinkAddress; -import android.net.LinkProperties; -import android.net.MacAddress; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.net.NetworkInfo; -import android.net.NetworkState; -import android.net.NetworkUtils; -import android.net.RouteInfo; -import android.net.dhcp.DhcpServerCallbacks; -import android.net.dhcp.DhcpServingParamsParcel; -import android.net.dhcp.IDhcpServer; -import android.net.ip.IpServer; -import android.net.ip.RouterAdvertisementDaemon; -import android.net.util.InterfaceParams; -import android.net.util.NetworkConstants; -import android.net.util.SharedLog; -import android.net.wifi.WifiConfiguration; -import android.net.wifi.WifiManager; -import android.os.Bundle; -import android.os.Handler; -import android.os.INetworkManagementService; -import android.os.PersistableBundle; -import android.os.RemoteException; -import android.os.UserHandle; -import android.os.UserManager; -import android.os.test.TestLooper; -import android.provider.Settings; -import android.telephony.CarrierConfigManager; -import android.telephony.PhoneStateListener; -import android.telephony.TelephonyManager; -import android.test.mock.MockContentResolver; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.internal.util.ArrayUtils; -import com.android.internal.util.StateMachine; -import com.android.internal.util.test.BroadcastInterceptingContext; -import com.android.internal.util.test.FakeSettingsProvider; -import com.android.server.connectivity.tethering.IPv6TetheringCoordinator; -import com.android.server.connectivity.tethering.OffloadHardwareInterface; -import com.android.server.connectivity.tethering.TetheringConfiguration; -import com.android.server.connectivity.tethering.TetheringDependencies; -import com.android.server.connectivity.tethering.UpstreamNetworkMonitor; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Vector; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class TetheringTest { - private static final int IFINDEX_OFFSET = 100; - - private static final String TEST_MOBILE_IFNAME = "test_rmnet_data0"; - private static final String TEST_XLAT_MOBILE_IFNAME = "v4-test_rmnet_data0"; - private static final String TEST_USB_IFNAME = "test_rndis0"; - private static final String TEST_WLAN_IFNAME = "test_wlan0"; - - private static final int DHCPSERVER_START_TIMEOUT_MS = 1000; - - @Mock private ApplicationInfo mApplicationInfo; - @Mock private Context mContext; - @Mock private INetworkManagementService mNMService; - @Mock private INetworkStatsService mStatsService; - @Mock private INetworkPolicyManager mPolicyManager; - @Mock private MockableSystemProperties mSystemProperties; - @Mock private OffloadHardwareInterface mOffloadHardwareInterface; - @Mock private Resources mResources; - @Mock private TelephonyManager mTelephonyManager; - @Mock private UsbManager mUsbManager; - @Mock private WifiManager mWifiManager; - @Mock private CarrierConfigManager mCarrierConfigManager; - @Mock private UpstreamNetworkMonitor mUpstreamNetworkMonitor; - @Mock private IPv6TetheringCoordinator mIPv6TetheringCoordinator; - @Mock private RouterAdvertisementDaemon mRouterAdvertisementDaemon; - @Mock private IDhcpServer mDhcpServer; - @Mock private INetd mNetd; - - private final MockIpServerDependencies mIpServerDependencies = - spy(new MockIpServerDependencies()); - private final MockTetheringDependencies mTetheringDependencies = - new MockTetheringDependencies(); - - // Like so many Android system APIs, these cannot be mocked because it is marked final. - // We have to use the real versions. - private final PersistableBundle mCarrierConfig = new PersistableBundle(); - private final TestLooper mLooper = new TestLooper(); - - private Vector<Intent> mIntents; - private BroadcastInterceptingContext mServiceContext; - private MockContentResolver mContentResolver; - private BroadcastReceiver mBroadcastReceiver; - private Tethering mTethering; - private PhoneStateListener mPhoneStateListener; - - private class MockContext extends BroadcastInterceptingContext { - MockContext(Context base) { - super(base); - } - - @Override - public ApplicationInfo getApplicationInfo() { return mApplicationInfo; } - - @Override - public ContentResolver getContentResolver() { return mContentResolver; } - - @Override - public String getPackageName() { return "TetheringTest"; } - - @Override - public Resources getResources() { return mResources; } - - @Override - public Object getSystemService(String name) { - if (Context.WIFI_SERVICE.equals(name)) return mWifiManager; - if (Context.USB_SERVICE.equals(name)) return mUsbManager; - if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager; - return super.getSystemService(name); - } - } - - public class MockIpServerDependencies extends IpServer.Dependencies { - @Override - public RouterAdvertisementDaemon getRouterAdvertisementDaemon( - InterfaceParams ifParams) { - return mRouterAdvertisementDaemon; - } - - @Override - public InterfaceParams getInterfaceParams(String ifName) { - assertTrue("Non-mocked interface " + ifName, - ifName.equals(TEST_USB_IFNAME) - || ifName.equals(TEST_WLAN_IFNAME) - || ifName.equals(TEST_MOBILE_IFNAME)); - final String[] ifaces = new String[] { - TEST_USB_IFNAME, TEST_WLAN_IFNAME, TEST_MOBILE_IFNAME }; - return new InterfaceParams(ifName, ArrayUtils.indexOf(ifaces, ifName) + IFINDEX_OFFSET, - MacAddress.ALL_ZEROS_ADDRESS); - } - - @Override - public INetd getNetdService() { - return mNetd; - } - - @Override - public void makeDhcpServer(String ifName, DhcpServingParamsParcel params, - DhcpServerCallbacks cb) { - new Thread(() -> { - try { - cb.onDhcpServerCreated(STATUS_SUCCESS, mDhcpServer); - } catch (RemoteException e) { - fail(e.getMessage()); - } - }).run(); - } - } - - private class MockTetheringConfiguration extends TetheringConfiguration { - MockTetheringConfiguration(Context ctx, SharedLog log, int id) { - super(ctx, log, id); - } - - @Override - protected Resources getResourcesForSubIdWrapper(Context ctx, int subId) { - return mResources; - } - } - - public class MockTetheringDependencies extends TetheringDependencies { - StateMachine upstreamNetworkMonitorMasterSM; - ArrayList<IpServer> ipv6CoordinatorNotifyList; - int isTetheringSupportedCalls; - - public void reset() { - upstreamNetworkMonitorMasterSM = null; - ipv6CoordinatorNotifyList = null; - isTetheringSupportedCalls = 0; - } - - @Override - public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) { - return mOffloadHardwareInterface; - } - - @Override - public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx, - StateMachine target, SharedLog log, int what) { - upstreamNetworkMonitorMasterSM = target; - return mUpstreamNetworkMonitor; - } - - @Override - public IPv6TetheringCoordinator getIPv6TetheringCoordinator( - ArrayList<IpServer> notifyList, SharedLog log) { - ipv6CoordinatorNotifyList = notifyList; - return mIPv6TetheringCoordinator; - } - - @Override - public IpServer.Dependencies getIpServerDependencies() { - return mIpServerDependencies; - } - - @Override - public boolean isTetheringSupported() { - isTetheringSupportedCalls++; - return true; - } - - @Override - public TetheringConfiguration generateTetheringConfiguration(Context ctx, SharedLog log, - int subId) { - return new MockTetheringConfiguration(ctx, log, subId); - } - } - - private static NetworkState buildMobileUpstreamState(boolean withIPv4, boolean withIPv6, - boolean with464xlat) { - final NetworkInfo info = new NetworkInfo(TYPE_MOBILE, 0, null, null); - info.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null); - final LinkProperties prop = new LinkProperties(); - prop.setInterfaceName(TEST_MOBILE_IFNAME); - - if (withIPv4) { - prop.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), - NetworkUtils.numericToInetAddress("10.0.0.1"), TEST_MOBILE_IFNAME)); - } - - if (withIPv6) { - prop.addDnsServer(NetworkUtils.numericToInetAddress("2001:db8::2")); - prop.addLinkAddress( - new LinkAddress(NetworkUtils.numericToInetAddress("2001:db8::"), - NetworkConstants.RFC7421_PREFIX_LENGTH)); - prop.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), - NetworkUtils.numericToInetAddress("2001:db8::1"), TEST_MOBILE_IFNAME)); - } - - if (with464xlat) { - final LinkProperties stackedLink = new LinkProperties(); - stackedLink.setInterfaceName(TEST_XLAT_MOBILE_IFNAME); - stackedLink.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), - NetworkUtils.numericToInetAddress("192.0.0.1"), TEST_XLAT_MOBILE_IFNAME)); - - prop.addStackedLink(stackedLink); - } - - - final NetworkCapabilities capabilities = new NetworkCapabilities() - .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);; - return new NetworkState(info, prop, capabilities, new Network(100), null, "netid"); - } - - private static NetworkState buildMobileIPv4UpstreamState() { - return buildMobileUpstreamState(true, false, false); - } - - private static NetworkState buildMobileIPv6UpstreamState() { - return buildMobileUpstreamState(false, true, false); - } - - private static NetworkState buildMobileDualStackUpstreamState() { - return buildMobileUpstreamState(true, true, false); - } - - private static NetworkState buildMobile464xlatUpstreamState() { - return buildMobileUpstreamState(false, true, true); - } - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - when(mResources.getStringArray(com.android.internal.R.array.config_tether_dhcp_range)) - .thenReturn(new String[0]); - when(mResources.getStringArray(com.android.internal.R.array.config_tether_usb_regexs)) - .thenReturn(new String[] { "test_rndis\\d" }); - when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_regexs)) - .thenReturn(new String[]{ "test_wlan\\d" }); - when(mResources.getStringArray(com.android.internal.R.array.config_tether_bluetooth_regexs)) - .thenReturn(new String[0]); - when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types)) - .thenReturn(new int[0]); - when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic)) - .thenReturn(false); - when(mNMService.listInterfaces()) - .thenReturn(new String[] { - TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME}); - when(mNMService.getInterfaceConfig(anyString())) - .thenReturn(new InterfaceConfiguration()); - when(mRouterAdvertisementDaemon.start()) - .thenReturn(true); - - mServiceContext = new MockContext(mContext); - mContentResolver = new MockContentResolver(mServiceContext); - mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); - Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0); - mIntents = new Vector<>(); - mBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - mIntents.addElement(intent); - } - }; - mServiceContext.registerReceiver(mBroadcastReceiver, - new IntentFilter(ACTION_TETHER_STATE_CHANGED)); - mTetheringDependencies.reset(); - mTethering = makeTethering(); - verify(mNMService).registerTetheringStatsProvider(any(), anyString()); - final ArgumentCaptor<PhoneStateListener> phoneListenerCaptor = - ArgumentCaptor.forClass(PhoneStateListener.class); - verify(mTelephonyManager).listen(phoneListenerCaptor.capture(), - eq(PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE)); - mPhoneStateListener = phoneListenerCaptor.getValue(); - } - - private Tethering makeTethering() { - return new Tethering(mServiceContext, mNMService, mStatsService, mPolicyManager, - mLooper.getLooper(), mSystemProperties, - mTetheringDependencies); - } - - @After - public void tearDown() { - mServiceContext.unregisterReceiver(mBroadcastReceiver); - } - - private void sendWifiApStateChanged(int state) { - final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); - intent.putExtra(EXTRA_WIFI_AP_STATE, state); - mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); - } - - private void sendWifiApStateChanged(int state, String ifname, int ipmode) { - final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); - intent.putExtra(EXTRA_WIFI_AP_STATE, state); - intent.putExtra(EXTRA_WIFI_AP_INTERFACE_NAME, ifname); - intent.putExtra(EXTRA_WIFI_AP_MODE, ipmode); - mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); - } - - private void sendUsbBroadcast(boolean connected, boolean configured, boolean rndisFunction) { - final Intent intent = new Intent(UsbManager.ACTION_USB_STATE); - intent.putExtra(USB_CONNECTED, connected); - intent.putExtra(USB_CONFIGURED, configured); - intent.putExtra(USB_FUNCTION_RNDIS, rndisFunction); - mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); - } - - private void sendConfigurationChanged() { - final Intent intent = new Intent(Intent.ACTION_CONFIGURATION_CHANGED); - mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); - } - - private void verifyInterfaceServingModeStarted() throws Exception { - verify(mNMService, times(1)).getInterfaceConfig(TEST_WLAN_IFNAME); - verify(mNMService, times(1)) - .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class)); - verify(mNMService, times(1)).tetherInterface(TEST_WLAN_IFNAME); - } - - private void verifyTetheringBroadcast(String ifname, String whichExtra) { - // Verify that ifname is in the whichExtra array of the tether state changed broadcast. - final Intent bcast = mIntents.get(0); - assertEquals(ACTION_TETHER_STATE_CHANGED, bcast.getAction()); - final ArrayList<String> ifnames = bcast.getStringArrayListExtra(whichExtra); - assertTrue(ifnames.contains(ifname)); - mIntents.remove(bcast); - } - - public void failingLocalOnlyHotspotLegacyApBroadcast( - boolean emulateInterfaceStatusChanged) throws Exception { - // Emulate externally-visible WifiManager effects, causing the - // per-interface state machine to start up, and telling us that - // hotspot mode is to be started. - if (emulateInterfaceStatusChanged) { - mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true); - } - sendWifiApStateChanged(WIFI_AP_STATE_ENABLED); - mLooper.dispatchAll(); - - // If, and only if, Tethering received an interface status changed then - // it creates a IpServer and sends out a broadcast indicating that the - // interface is "available". - if (emulateInterfaceStatusChanged) { - assertEquals(1, mTetheringDependencies.isTetheringSupportedCalls); - verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER); - } - verifyNoMoreInteractions(mNMService); - verifyNoMoreInteractions(mWifiManager); - } - - private void prepareUsbTethering(NetworkState upstreamState) { - when(mUpstreamNetworkMonitor.getCurrentPreferredUpstream()).thenReturn(upstreamState); - when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any())) - .thenReturn(upstreamState); - - // Emulate pressing the USB tethering button in Settings UI. - mTethering.startTethering(TETHERING_USB, null, false); - mLooper.dispatchAll(); - verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS); - - mTethering.interfaceStatusChanged(TEST_USB_IFNAME, true); - } - - @Test - public void testUsbConfiguredBroadcastStartsTethering() throws Exception { - NetworkState upstreamState = buildMobileIPv4UpstreamState(); - prepareUsbTethering(upstreamState); - - // This should produce no activity of any kind. - verifyNoMoreInteractions(mNMService); - - // Pretend we then receive USB configured broadcast. - sendUsbBroadcast(true, true, true); - mLooper.dispatchAll(); - // Now we should see the start of tethering mechanics (in this case: - // tetherMatchingInterfaces() which starts by fetching all interfaces). - verify(mNMService, times(1)).listInterfaces(); - - // UpstreamNetworkMonitor should receive selected upstream - verify(mUpstreamNetworkMonitor, times(1)).selectPreferredUpstreamType(any()); - verify(mUpstreamNetworkMonitor, times(1)).setCurrentUpstream(upstreamState.network); - } - - @Test - public void failingLocalOnlyHotspotLegacyApBroadcastWithIfaceStatusChanged() throws Exception { - failingLocalOnlyHotspotLegacyApBroadcast(true); - } - - @Test - public void failingLocalOnlyHotspotLegacyApBroadcastSansIfaceStatusChanged() throws Exception { - failingLocalOnlyHotspotLegacyApBroadcast(false); - } - - public void workingLocalOnlyHotspotEnrichedApBroadcast( - boolean emulateInterfaceStatusChanged) throws Exception { - // Emulate externally-visible WifiManager effects, causing the - // per-interface state machine to start up, and telling us that - // hotspot mode is to be started. - if (emulateInterfaceStatusChanged) { - mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true); - } - sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_LOCAL_ONLY); - mLooper.dispatchAll(); - - verifyInterfaceServingModeStarted(); - verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER); - verify(mNMService, times(1)).setIpForwardingEnabled(true); - verify(mNMService, times(1)).startTethering(any(String[].class)); - verifyNoMoreInteractions(mNMService); - verify(mWifiManager).updateInterfaceIpState( - TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_LOCAL_ONLY); - verifyNoMoreInteractions(mWifiManager); - verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY); - verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks(); - // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast(). - assertTrue(1 <= mTetheringDependencies.isTetheringSupportedCalls); - - // Emulate externally-visible WifiManager effects, when hotspot mode - // is being torn down. - sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED); - mTethering.interfaceRemoved(TEST_WLAN_IFNAME); - mLooper.dispatchAll(); - - verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME); - // TODO: Why is {g,s}etInterfaceConfig() called more than once? - verify(mNMService, atLeastOnce()).getInterfaceConfig(TEST_WLAN_IFNAME); - verify(mNMService, atLeastOnce()) - .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class)); - verify(mNMService, times(1)).stopTethering(); - verify(mNMService, times(1)).setIpForwardingEnabled(false); - verifyNoMoreInteractions(mNMService); - verifyNoMoreInteractions(mWifiManager); - // Asking for the last error after the per-interface state machine - // has been reaped yields an unknown interface error. - assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_WLAN_IFNAME)); - } - - /** - * Send CMD_IPV6_TETHER_UPDATE to IpServers as would be done by IPv6TetheringCoordinator. - */ - private void sendIPv6TetherUpdates(NetworkState upstreamState) { - // IPv6TetheringCoordinator must have been notified of downstream - verify(mIPv6TetheringCoordinator, times(1)).addActiveDownstream( - argThat(sm -> sm.linkProperties().getInterfaceName().equals(TEST_USB_IFNAME)), - eq(IpServer.STATE_TETHERED)); - - for (IpServer ipSrv : - mTetheringDependencies.ipv6CoordinatorNotifyList) { - NetworkState ipv6OnlyState = buildMobileUpstreamState(false, true, false); - ipSrv.sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, 0, 0, - upstreamState.linkProperties.isIpv6Provisioned() - ? ipv6OnlyState.linkProperties - : null); - } - mLooper.dispatchAll(); - } - - private void runUsbTethering(NetworkState upstreamState) { - prepareUsbTethering(upstreamState); - sendUsbBroadcast(true, true, true); - mLooper.dispatchAll(); - } - - @Test - public void workingMobileUsbTethering_IPv4() throws Exception { - NetworkState upstreamState = buildMobileIPv4UpstreamState(); - runUsbTethering(upstreamState); - - verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); - verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); - - sendIPv6TetherUpdates(upstreamState); - verify(mRouterAdvertisementDaemon, never()).buildNewRa(any(), notNull()); - verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any()); - } - - @Test - public void workingMobileUsbTethering_IPv4LegacyDhcp() { - Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1); - mTethering = makeTethering(); - final NetworkState upstreamState = buildMobileIPv4UpstreamState(); - runUsbTethering(upstreamState); - sendIPv6TetherUpdates(upstreamState); - - verify(mIpServerDependencies, never()).makeDhcpServer(any(), any(), any()); - } - - @Test - public void workingMobileUsbTethering_IPv6() throws Exception { - NetworkState upstreamState = buildMobileIPv6UpstreamState(); - runUsbTethering(upstreamState); - - verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); - verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); - - sendIPv6TetherUpdates(upstreamState); - verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull()); - verify(mNetd, times(1)).tetherApplyDnsInterfaces(); - } - - @Test - public void workingMobileUsbTethering_DualStack() throws Exception { - NetworkState upstreamState = buildMobileDualStackUpstreamState(); - runUsbTethering(upstreamState); - - verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); - verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); - verify(mRouterAdvertisementDaemon, times(1)).start(); - verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any()); - - sendIPv6TetherUpdates(upstreamState); - verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull()); - verify(mNetd, times(1)).tetherApplyDnsInterfaces(); - } - - @Test - public void workingMobileUsbTethering_MultipleUpstreams() throws Exception { - NetworkState upstreamState = buildMobile464xlatUpstreamState(); - runUsbTethering(upstreamState); - - verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME); - verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); - verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any()); - verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); - verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, - TEST_XLAT_MOBILE_IFNAME); - - sendIPv6TetherUpdates(upstreamState); - verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull()); - verify(mNetd, times(1)).tetherApplyDnsInterfaces(); - } - - @Test - public void workingMobileUsbTethering_v6Then464xlat() throws Exception { - // Setup IPv6 - NetworkState upstreamState = buildMobileIPv6UpstreamState(); - runUsbTethering(upstreamState); - - verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); - verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any()); - verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); - - // Then 464xlat comes up - upstreamState = buildMobile464xlatUpstreamState(); - when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any())) - .thenReturn(upstreamState); - - // Upstream LinkProperties changed: UpstreamNetworkMonitor sends EVENT_ON_LINKPROPERTIES. - mTetheringDependencies.upstreamNetworkMonitorMasterSM.sendMessage( - Tethering.TetherMasterSM.EVENT_UPSTREAM_CALLBACK, - UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES, - 0, - upstreamState); - mLooper.dispatchAll(); - - // Forwarding is added for 464xlat - verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME); - verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, - TEST_XLAT_MOBILE_IFNAME); - // Forwarding was not re-added for v6 (still times(1)) - verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); - verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); - // DHCP not restarted on downstream (still times(1)) - verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any()); - } - - @Test - public void configTetherUpstreamAutomaticIgnoresConfigTetherUpstreamTypes() throws Exception { - when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic)) - .thenReturn(true); - sendConfigurationChanged(); - - // Setup IPv6 - final NetworkState upstreamState = buildMobileIPv6UpstreamState(); - runUsbTethering(upstreamState); - - // UpstreamNetworkMonitor should choose upstream automatically - // (in this specific case: choose the default network). - verify(mUpstreamNetworkMonitor, times(1)).getCurrentPreferredUpstream(); - verify(mUpstreamNetworkMonitor, never()).selectPreferredUpstreamType(any()); - - verify(mUpstreamNetworkMonitor, times(1)).setCurrentUpstream(upstreamState.network); - } - - @Test - public void workingLocalOnlyHotspotEnrichedApBroadcastWithIfaceChanged() throws Exception { - workingLocalOnlyHotspotEnrichedApBroadcast(true); - } - - @Test - public void workingLocalOnlyHotspotEnrichedApBroadcastSansIfaceChanged() throws Exception { - workingLocalOnlyHotspotEnrichedApBroadcast(false); - } - - // TODO: Test with and without interfaceStatusChanged(). - @Test - public void failingWifiTetheringLegacyApBroadcast() throws Exception { - when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true); - - // Emulate pressing the WiFi tethering button. - mTethering.startTethering(TETHERING_WIFI, null, false); - mLooper.dispatchAll(); - verify(mWifiManager, times(1)).startSoftAp(null); - verifyNoMoreInteractions(mWifiManager); - verifyNoMoreInteractions(mNMService); - - // Emulate externally-visible WifiManager effects, causing the - // per-interface state machine to start up, and telling us that - // tethering mode is to be started. - mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true); - sendWifiApStateChanged(WIFI_AP_STATE_ENABLED); - mLooper.dispatchAll(); - - assertEquals(1, mTetheringDependencies.isTetheringSupportedCalls); - verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER); - verifyNoMoreInteractions(mNMService); - verifyNoMoreInteractions(mWifiManager); - } - - // TODO: Test with and without interfaceStatusChanged(). - @Test - public void workingWifiTetheringEnrichedApBroadcast() throws Exception { - when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true); - - // Emulate pressing the WiFi tethering button. - mTethering.startTethering(TETHERING_WIFI, null, false); - mLooper.dispatchAll(); - verify(mWifiManager, times(1)).startSoftAp(null); - verifyNoMoreInteractions(mWifiManager); - verifyNoMoreInteractions(mNMService); - - // Emulate externally-visible WifiManager effects, causing the - // per-interface state machine to start up, and telling us that - // tethering mode is to be started. - mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true); - sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); - mLooper.dispatchAll(); - - verifyInterfaceServingModeStarted(); - verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER); - verify(mNMService, times(1)).setIpForwardingEnabled(true); - verify(mNMService, times(1)).startTethering(any(String[].class)); - verifyNoMoreInteractions(mNMService); - verify(mWifiManager).updateInterfaceIpState( - TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED); - verifyNoMoreInteractions(mWifiManager); - verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_TETHER); - verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks(); - // In tethering mode, in the default configuration, an explicit request - // for a mobile network is also made. - verify(mUpstreamNetworkMonitor, times(1)).registerMobileNetworkRequest(); - // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast(). - assertTrue(1 <= mTetheringDependencies.isTetheringSupportedCalls); - - ///// - // We do not currently emulate any upstream being found. - // - // This is why there are no calls to verify mNMService.enableNat() or - // mNMService.startInterfaceForwarding(). - ///// - - // Emulate pressing the WiFi tethering button. - mTethering.stopTethering(TETHERING_WIFI); - mLooper.dispatchAll(); - verify(mWifiManager, times(1)).stopSoftAp(); - verifyNoMoreInteractions(mWifiManager); - verifyNoMoreInteractions(mNMService); - - // Emulate externally-visible WifiManager effects, when tethering mode - // is being torn down. - sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED); - mTethering.interfaceRemoved(TEST_WLAN_IFNAME); - mLooper.dispatchAll(); - - verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME); - // TODO: Why is {g,s}etInterfaceConfig() called more than once? - verify(mNMService, atLeastOnce()).getInterfaceConfig(TEST_WLAN_IFNAME); - verify(mNMService, atLeastOnce()) - .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class)); - verify(mNMService, times(1)).stopTethering(); - verify(mNMService, times(1)).setIpForwardingEnabled(false); - verifyNoMoreInteractions(mNMService); - verifyNoMoreInteractions(mWifiManager); - // Asking for the last error after the per-interface state machine - // has been reaped yields an unknown interface error. - assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_WLAN_IFNAME)); - } - - // TODO: Test with and without interfaceStatusChanged(). - @Test - public void failureEnablingIpForwarding() throws Exception { - when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true); - doThrow(new RemoteException()).when(mNMService).setIpForwardingEnabled(true); - - // Emulate pressing the WiFi tethering button. - mTethering.startTethering(TETHERING_WIFI, null, false); - mLooper.dispatchAll(); - verify(mWifiManager, times(1)).startSoftAp(null); - verifyNoMoreInteractions(mWifiManager); - verifyNoMoreInteractions(mNMService); - - // Emulate externally-visible WifiManager effects, causing the - // per-interface state machine to start up, and telling us that - // tethering mode is to be started. - mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true); - sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); - mLooper.dispatchAll(); - - // We verify get/set called thrice here: twice for setup (on NMService) and once during - // teardown (on Netd) because all events happen over the course of the single - // dispatchAll() above. Note that once the IpServer IPv4 address config - // code is refactored the two calls during shutdown will revert to one. - verify(mNMService, times(2)).getInterfaceConfig(TEST_WLAN_IFNAME); - verify(mNMService, times(2)) - .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class)); - verify(mNetd, times(1)).interfaceSetCfg(argThat(p -> TEST_WLAN_IFNAME.equals(p.ifName))); - verify(mNMService, times(1)).tetherInterface(TEST_WLAN_IFNAME); - verify(mWifiManager).updateInterfaceIpState( - TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED); - // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast(). - assertTrue(1 <= mTetheringDependencies.isTetheringSupportedCalls); - verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER); - // This is called, but will throw. - verify(mNMService, times(1)).setIpForwardingEnabled(true); - // This never gets called because of the exception thrown above. - verify(mNMService, times(0)).startTethering(any(String[].class)); - // When the master state machine transitions to an error state it tells - // downstream interfaces, which causes us to tell Wi-Fi about the error - // so it can take down AP mode. - verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME); - verify(mWifiManager).updateInterfaceIpState( - TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR); - - verifyNoMoreInteractions(mWifiManager); - verifyNoMoreInteractions(mNMService); - } - - private void userRestrictionsListenerBehaviour( - boolean currentDisallow, boolean nextDisallow, String[] activeTetheringIfacesList, - int expectedInteractionsWithShowNotification) throws Exception { - final int userId = 0; - final Bundle currRestrictions = new Bundle(); - final Bundle newRestrictions = new Bundle(); - Tethering tethering = mock(Tethering.class); - Tethering.TetheringUserRestrictionListener turl = - new Tethering.TetheringUserRestrictionListener(tethering); - - currRestrictions.putBoolean(UserManager.DISALLOW_CONFIG_TETHERING, currentDisallow); - newRestrictions.putBoolean(UserManager.DISALLOW_CONFIG_TETHERING, nextDisallow); - when(tethering.getTetheredIfaces()).thenReturn(activeTetheringIfacesList); - - turl.onUserRestrictionsChanged(userId, newRestrictions, currRestrictions); - - verify(tethering, times(expectedInteractionsWithShowNotification)) - .showTetheredNotification(anyInt(), eq(false)); - - verify(tethering, times(expectedInteractionsWithShowNotification)).untetherAll(); - } - - @Test - public void testDisallowTetheringWhenNoTetheringInterfaceIsActive() throws Exception { - final String[] emptyActiveIfacesList = new String[]{}; - final boolean currDisallow = false; - final boolean nextDisallow = true; - final int expectedInteractionsWithShowNotification = 0; - - userRestrictionsListenerBehaviour(currDisallow, nextDisallow, emptyActiveIfacesList, - expectedInteractionsWithShowNotification); - } - - @Test - public void testDisallowTetheringWhenAtLeastOneTetheringInterfaceIsActive() throws Exception { - final String[] nonEmptyActiveIfacesList = new String[]{TEST_WLAN_IFNAME}; - final boolean currDisallow = false; - final boolean nextDisallow = true; - final int expectedInteractionsWithShowNotification = 1; - - userRestrictionsListenerBehaviour(currDisallow, nextDisallow, nonEmptyActiveIfacesList, - expectedInteractionsWithShowNotification); - } - - @Test - public void testAllowTetheringWhenNoTetheringInterfaceIsActive() throws Exception { - final String[] nonEmptyActiveIfacesList = new String[]{}; - final boolean currDisallow = true; - final boolean nextDisallow = false; - final int expectedInteractionsWithShowNotification = 0; - - userRestrictionsListenerBehaviour(currDisallow, nextDisallow, nonEmptyActiveIfacesList, - expectedInteractionsWithShowNotification); - } - - @Test - public void testAllowTetheringWhenAtLeastOneTetheringInterfaceIsActive() throws Exception { - final String[] nonEmptyActiveIfacesList = new String[]{TEST_WLAN_IFNAME}; - final boolean currDisallow = true; - final boolean nextDisallow = false; - final int expectedInteractionsWithShowNotification = 0; - - userRestrictionsListenerBehaviour(currDisallow, nextDisallow, nonEmptyActiveIfacesList, - expectedInteractionsWithShowNotification); - } - - @Test - public void testDisallowTetheringUnchanged() throws Exception { - final String[] nonEmptyActiveIfacesList = new String[]{TEST_WLAN_IFNAME}; - final int expectedInteractionsWithShowNotification = 0; - boolean currDisallow = true; - boolean nextDisallow = true; - - userRestrictionsListenerBehaviour(currDisallow, nextDisallow, nonEmptyActiveIfacesList, - expectedInteractionsWithShowNotification); - - currDisallow = false; - nextDisallow = false; - - userRestrictionsListenerBehaviour(currDisallow, nextDisallow, nonEmptyActiveIfacesList, - expectedInteractionsWithShowNotification); - } - - private class TestTetheringEventCallback extends ITetheringEventCallback.Stub { - private final ArrayList<Network> mActualUpstreams = new ArrayList<>(); - - public void expectUpstreamChanged(Network... networks) { - final ArrayList<Network> expectedUpstreams = - new ArrayList<Network>(Arrays.asList(networks)); - for (Network upstream : expectedUpstreams) { - // throws OOB if no expectations - assertEquals(mActualUpstreams.remove(0), upstream); - } - assertNoCallback(); - } - - @Override - public void onUpstreamChanged(Network network) { - mActualUpstreams.add(network); - } - - public void assertNoCallback() { - assertTrue(mActualUpstreams.isEmpty()); - } - } - - @Test - public void testRegisterTetheringEventCallback() throws Exception { - TestTetheringEventCallback callback1 = new TestTetheringEventCallback(); - TestTetheringEventCallback callback2 = new TestTetheringEventCallback(); - - // 1. Register one callback and run usb tethering. - mTethering.registerTetheringEventCallback(callback1); - mLooper.dispatchAll(); - callback1.expectUpstreamChanged(new Network[] {null}); - NetworkState upstreamState = buildMobileDualStackUpstreamState(); - runUsbTethering(upstreamState); - callback1.expectUpstreamChanged(upstreamState.network); - // 2. Register second callback. - mTethering.registerTetheringEventCallback(callback2); - mLooper.dispatchAll(); - callback2.expectUpstreamChanged(upstreamState.network); - // 3. Disable usb tethering. - mTethering.stopTethering(TETHERING_USB); - mLooper.dispatchAll(); - sendUsbBroadcast(false, false, false); - mLooper.dispatchAll(); - callback1.expectUpstreamChanged(new Network[] {null}); - callback2.expectUpstreamChanged(new Network[] {null}); - // 4. Unregister first callback and run hotspot. - mTethering.unregisterTetheringEventCallback(callback1); - mLooper.dispatchAll(); - when(mUpstreamNetworkMonitor.getCurrentPreferredUpstream()).thenReturn(upstreamState); - when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any())) - .thenReturn(upstreamState); - when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true); - mTethering.startTethering(TETHERING_WIFI, null, false); - mLooper.dispatchAll(); - mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true); - sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); - mLooper.dispatchAll(); - callback1.assertNoCallback(); - callback2.expectUpstreamChanged(upstreamState.network); - } - - @Test - public void testMultiSimAware() throws Exception { - final TetheringConfiguration initailConfig = mTethering.getTetheringConfiguration(); - assertEquals(INVALID_SUBSCRIPTION_ID, initailConfig.subId); - - final int fakeSubId = 1234; - mPhoneStateListener.onActiveDataSubscriptionIdChanged(fakeSubId); - final TetheringConfiguration newConfig = mTethering.getTetheringConfiguration(); - assertEquals(fakeSubId, newConfig.subId); - } - - // TODO: Test that a request for hotspot mode doesn't interfere with an - // already operating tethering mode interface. -} diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index ce50bef53d75..4ccf79a0cb37 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -25,14 +25,15 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; -import static android.net.RouteInfo.RTN_UNREACHABLE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.AdditionalMatchers.aryEq; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -43,6 +44,7 @@ import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -58,21 +60,28 @@ import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.content.res.Resources; import android.net.ConnectivityManager; +import android.net.Ikev2VpnProfile; +import android.net.InetAddresses; import android.net.IpPrefix; +import android.net.IpSecManager; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo.DetailedState; import android.net.RouteInfo; import android.net.UidRange; +import android.net.VpnManager; import android.net.VpnService; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.INetworkManagementService; import android.os.Looper; -import android.os.SystemClock; +import android.os.Process; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; +import android.security.Credentials; +import android.security.KeyStore; import android.util.ArrayMap; import android.util.ArraySet; @@ -81,6 +90,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; import com.android.internal.net.VpnConfig; +import com.android.internal.net.VpnProfile; +import com.android.server.IpSecService; import org.junit.Before; import org.junit.Test; @@ -91,8 +102,6 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -124,6 +133,11 @@ public class VpnTest { managedProfileA.profileGroupId = primaryUser.id; } + static final String TEST_VPN_PKG = "com.dummy.vpn"; + private static final String TEST_VPN_SERVER = "1.2.3.4"; + private static final String TEST_VPN_IDENTITY = "identity"; + private static final byte[] TEST_VPN_PSK = "psk".getBytes(); + /** * Names and UIDs for some fake packages. Important points: * - UID is ordered increasing. @@ -147,25 +161,47 @@ public class VpnTest { @Mock private AppOpsManager mAppOps; @Mock private NotificationManager mNotificationManager; @Mock private Vpn.SystemServices mSystemServices; + @Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator; @Mock private ConnectivityManager mConnectivityManager; + @Mock private IpSecService mIpSecService; + @Mock private KeyStore mKeyStore; + private final VpnProfile mVpnProfile; + + private IpSecManager mIpSecManager; + + public VpnTest() throws Exception { + // Build an actual VPN profile that is capable of being converted to and from an + // Ikev2VpnProfile + final Ikev2VpnProfile.Builder builder = + new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY); + builder.setAuthPsk(TEST_VPN_PSK); + mVpnProfile = builder.build().toVpnProfile(); + } @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mIpSecManager = new IpSecManager(mContext, mIpSecService); + when(mContext.getPackageManager()).thenReturn(mPackageManager); setMockedPackages(mPackages); - when(mContext.getPackageName()).thenReturn(Vpn.class.getPackage().getName()); + when(mContext.getPackageName()).thenReturn(TEST_VPN_PKG); + when(mContext.getOpPackageName()).thenReturn(TEST_VPN_PKG); when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager); when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps); when(mContext.getSystemService(eq(Context.NOTIFICATION_SERVICE))) .thenReturn(mNotificationManager); when(mContext.getSystemService(eq(Context.CONNECTIVITY_SERVICE))) .thenReturn(mConnectivityManager); + when(mContext.getSystemService(eq(Context.IPSEC_SERVICE))).thenReturn(mIpSecManager); when(mContext.getString(R.string.config_customVpnAlwaysOnDisconnectedDialogComponent)) .thenReturn(Resources.getSystem().getString( R.string.config_customVpnAlwaysOnDisconnectedDialogComponent)); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS)) + .thenReturn(true); + when(mSystemServices.isCallerSystem()).thenReturn(true); // Used by {@link Notification.Builder} ApplicationInfo applicationInfo = new ApplicationInfo(); @@ -175,6 +211,10 @@ public class VpnTest { .thenReturn(applicationInfo); doNothing().when(mNetService).registerObserver(any()); + + // Deny all appops by default. + when(mAppOps.noteOpNoThrow(anyInt(), anyInt(), anyString())) + .thenReturn(AppOpsManager.MODE_IGNORED); } @Test @@ -251,17 +291,17 @@ public class VpnTest { assertFalse(vpn.getLockdown()); // Set always-on without lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList())); + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList(), mKeyStore)); assertTrue(vpn.getAlwaysOn()); assertFalse(vpn.getLockdown()); // Set always-on with lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList())); + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList(), mKeyStore)); assertTrue(vpn.getAlwaysOn()); assertTrue(vpn.getLockdown()); // Remove always-on configuration. - assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList())); + assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList(), mKeyStore)); assertFalse(vpn.getAlwaysOn()); assertFalse(vpn.getLockdown()); } @@ -275,11 +315,11 @@ public class VpnTest { assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]); // Set always-on without lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null, mKeyStore)); assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]); // Set always-on with lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null, mKeyStore)); verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] { new UidRange(user.start, user.start + PKG_UIDS[1] - 1), new UidRange(user.start + PKG_UIDS[1] + 1, user.stop) @@ -288,7 +328,7 @@ public class VpnTest { assertUnblocked(vpn, user.start + PKG_UIDS[1]); // Switch to another app. - assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore)); verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] { new UidRange(user.start, user.start + PKG_UIDS[1] - 1), new UidRange(user.start + PKG_UIDS[1] + 1, user.stop) @@ -307,7 +347,8 @@ public class VpnTest { final UidRange user = UidRange.createForUser(primaryUser.id); // Set always-on with lockdown and whitelist app PKGS[2] from lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[2]))); + assertTrue(vpn.setAlwaysOnPackage( + PKGS[1], true, Collections.singletonList(PKGS[2]), mKeyStore)); verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] { new UidRange(user.start, user.start + PKG_UIDS[1] - 1), new UidRange(user.start + PKG_UIDS[2] + 1, user.stop) @@ -316,7 +357,8 @@ public class VpnTest { assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]); // Change whitelisted app to PKGS[3]. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[3]))); + assertTrue(vpn.setAlwaysOnPackage( + PKGS[1], true, Collections.singletonList(PKGS[3]), mKeyStore)); verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] { new UidRange(user.start + PKG_UIDS[2] + 1, user.stop) })); @@ -328,7 +370,8 @@ public class VpnTest { assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[3]); // Change the VPN app. - assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[3]))); + assertTrue(vpn.setAlwaysOnPackage( + PKGS[0], true, Collections.singletonList(PKGS[3]), mKeyStore)); verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] { new UidRange(user.start, user.start + PKG_UIDS[1] - 1), new UidRange(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1) @@ -341,7 +384,7 @@ public class VpnTest { assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]); // Remove the whitelist. - assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null, mKeyStore)); verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] { new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1), new UidRange(user.start + PKG_UIDS[3] + 1, user.stop) @@ -354,7 +397,8 @@ public class VpnTest { assertUnblocked(vpn, user.start + PKG_UIDS[0]); // Add the whitelist. - assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[1]))); + assertTrue(vpn.setAlwaysOnPackage( + PKGS[0], true, Collections.singletonList(PKGS[1]), mKeyStore)); verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] { new UidRange(user.start + PKG_UIDS[0] + 1, user.stop) })); @@ -366,12 +410,13 @@ public class VpnTest { assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1]); // Try whitelisting a package with a comma, should be rejected. - assertFalse(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList("a.b,c.d"))); + assertFalse(vpn.setAlwaysOnPackage( + PKGS[0], true, Collections.singletonList("a.b,c.d"), mKeyStore)); // Pass a non-existent packages in the whitelist, they (and only they) should be ignored. // Whitelisted package should change from PGKS[1] to PKGS[2]. - assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, - Arrays.asList("com.foo.app", PKGS[2], "com.bar.app"))); + assertTrue(vpn.setAlwaysOnPackage( + PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app"), mKeyStore)); verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[]{ new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1), new UidRange(user.start + PKG_UIDS[1] + 1, user.stop) @@ -396,7 +441,7 @@ public class VpnTest { final UidRange profile = UidRange.createForUser(tempProfile.id); // Set lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null)); + assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore)); verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] { new UidRange(user.start, user.start + PKG_UIDS[3] - 1), new UidRange(user.start + PKG_UIDS[3] + 1, user.stop) @@ -464,12 +509,12 @@ public class VpnTest { order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser)); // When a new VPN package is set the rules should change to cover that package. - vpn.prepare(null, PKGS[0]); + vpn.prepare(null, PKGS[0], VpnManager.TYPE_VPN_SERVICE); order.verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(entireUser)); order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(exceptPkg0)); // When that VPN package is unset, everything should be undone again in reverse. - vpn.prepare(null, VpnConfig.LEGACY_VPN); + vpn.prepare(null, VpnConfig.LEGACY_VPN, VpnManager.TYPE_VPN_SERVICE); order.verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(exceptPkg0)); order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser)); } @@ -490,22 +535,22 @@ public class VpnTest { .thenReturn(Collections.singletonList(resInfo)); // null package name should return false - assertFalse(vpn.isAlwaysOnPackageSupported(null)); + assertFalse(vpn.isAlwaysOnPackageSupported(null, mKeyStore)); // Pre-N apps are not supported appInfo.targetSdkVersion = VERSION_CODES.M; - assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0])); + assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore)); // N+ apps are supported by default appInfo.targetSdkVersion = VERSION_CODES.N; - assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0])); + assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore)); // Apps that opt out explicitly are not supported appInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT; Bundle metaData = new Bundle(); metaData.putBoolean(VpnService.SERVICE_META_DATA_SUPPORTS_ALWAYS_ON, false); svcInfo.metaData = metaData; - assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0])); + assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore)); } @Test @@ -522,7 +567,7 @@ public class VpnTest { .cancelAsUser(anyString(), anyInt(), eq(userHandle)); // Start showing a notification for disconnected once always-on. - vpn.setAlwaysOnPackage(PKGS[0], false, null); + vpn.setAlwaysOnPackage(PKGS[0], false, null, mKeyStore); order.verify(mNotificationManager) .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle)); @@ -536,7 +581,7 @@ public class VpnTest { .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle)); // Notification should be cleared after unsetting always-on package. - vpn.setAlwaysOnPackage(null, false, null); + vpn.setAlwaysOnPackage(null, false, null, mKeyStore); order.verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), eq(userHandle)); } @@ -564,6 +609,7 @@ public class VpnTest { .addCapability(NET_CAPABILITY_NOT_METERED) .addCapability(NET_CAPABILITY_NOT_ROAMING) .addCapability(NET_CAPABILITY_NOT_CONGESTED) + .addCapability(NET_CAPABILITY_NOT_SUSPENDED) .setLinkUpstreamBandwidthKbps(20)); setMockedNetworks(networks); @@ -579,6 +625,7 @@ public class VpnTest { assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_METERED)); assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED)); + assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); Vpn.applyUnderlyingCapabilities( mConnectivityManager, @@ -593,6 +640,7 @@ public class VpnTest { assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_METERED)); assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED)); + assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); Vpn.applyUnderlyingCapabilities( mConnectivityManager, new Network[] {wifi}, caps, false /* isAlwaysMetered */); @@ -604,6 +652,7 @@ public class VpnTest { assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_METERED)); assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED)); + assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); Vpn.applyUnderlyingCapabilities( mConnectivityManager, new Network[] {wifi}, caps, true /* isAlwaysMetered */); @@ -615,6 +664,7 @@ public class VpnTest { assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_METERED)); assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED)); + assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); Vpn.applyUnderlyingCapabilities( mConnectivityManager, @@ -629,13 +679,365 @@ public class VpnTest { assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_METERED)); assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED)); + assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + } + + /** + * The profile name should NOT change between releases for backwards compatibility + * + * <p>If this is changed between releases, the {@link Vpn#getVpnProfilePrivileged()} method MUST + * be updated to ensure backward compatibility. + */ + @Test + public void testGetProfileNameForPackage() throws Exception { + final Vpn vpn = createVpn(primaryUser.id); + setMockedUsers(primaryUser); + + final String expected = Credentials.PLATFORM_VPN + primaryUser.id + "_" + TEST_VPN_PKG; + assertEquals(expected, vpn.getProfileNameForPackage(TEST_VPN_PKG)); + } + + private Vpn createVpnAndSetupUidChecks(int... grantedOps) throws Exception { + return createVpnAndSetupUidChecks(primaryUser, grantedOps); + } + + private Vpn createVpnAndSetupUidChecks(UserInfo user, int... grantedOps) throws Exception { + final Vpn vpn = createVpn(user.id); + setMockedUsers(user); + + when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt())) + .thenReturn(Process.myUid()); + + for (final int op : grantedOps) { + when(mAppOps.noteOpNoThrow(op, Process.myUid(), TEST_VPN_PKG)) + .thenReturn(AppOpsManager.MODE_ALLOWED); + } + + return vpn; + } + + private void checkProvisionVpnProfile(Vpn vpn, boolean expectedResult, int... checkedOps) { + assertEquals(expectedResult, vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile, mKeyStore)); + + // The profile should always be stored, whether or not consent has been previously granted. + verify(mKeyStore) + .put( + eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)), + eq(mVpnProfile.encode()), + eq(Process.SYSTEM_UID), + eq(0)); + + for (final int checkedOp : checkedOps) { + verify(mAppOps).noteOpNoThrow(checkedOp, Process.myUid(), TEST_VPN_PKG); + } + } + + @Test + public void testProvisionVpnProfileNoIpsecTunnels() throws Exception { + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS)) + .thenReturn(false); + final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + + try { + checkProvisionVpnProfile( + vpn, true /* expectedResult */, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + fail("Expected exception due to missing feature"); + } catch (UnsupportedOperationException expected) { + } + } + + @Test + public void testProvisionVpnProfilePreconsented() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + + checkProvisionVpnProfile( + vpn, true /* expectedResult */, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + } + + @Test + public void testProvisionVpnProfileNotPreconsented() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(); + + // Expect that both the ACTIVATE_VPN and ACTIVATE_PLATFORM_VPN were tried, but the caller + // had neither. + checkProvisionVpnProfile(vpn, false /* expectedResult */, + AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, AppOpsManager.OP_ACTIVATE_VPN); + } + + @Test + public void testProvisionVpnProfileVpnServicePreconsented() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OP_ACTIVATE_VPN); + + checkProvisionVpnProfile(vpn, true /* expectedResult */, AppOpsManager.OP_ACTIVATE_VPN); + } + + @Test + public void testProvisionVpnProfileTooLarge() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + + final VpnProfile bigProfile = new VpnProfile(""); + bigProfile.name = new String(new byte[Vpn.MAX_VPN_PROFILE_SIZE_BYTES + 1]); + + try { + vpn.provisionVpnProfile(TEST_VPN_PKG, bigProfile, mKeyStore); + fail("Expected IAE due to profile size"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testProvisionVpnProfileRestrictedUser() throws Exception { + final Vpn vpn = + createVpnAndSetupUidChecks( + restrictedProfileA, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + + try { + vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile, mKeyStore); + fail("Expected SecurityException due to restricted user"); + } catch (SecurityException expected) { + } + } + + @Test + public void testDeleteVpnProfile() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(); + + vpn.deleteVpnProfile(TEST_VPN_PKG, mKeyStore); + + verify(mKeyStore) + .delete(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)), eq(Process.SYSTEM_UID)); + } + + @Test + public void testDeleteVpnProfileRestrictedUser() throws Exception { + final Vpn vpn = + createVpnAndSetupUidChecks( + restrictedProfileA, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + + try { + vpn.deleteVpnProfile(TEST_VPN_PKG, mKeyStore); + fail("Expected SecurityException due to restricted user"); + } catch (SecurityException expected) { + } + } + + @Test + public void testGetVpnProfilePrivileged() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(); + + when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) + .thenReturn(new VpnProfile("").encode()); + + vpn.getVpnProfilePrivileged(TEST_VPN_PKG, mKeyStore); + + verify(mKeyStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); + } + + @Test + public void testStartVpnProfile() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + + when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) + .thenReturn(mVpnProfile.encode()); + + vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore); + + verify(mKeyStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); + verify(mAppOps) + .noteOpNoThrow( + eq(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN), + eq(Process.myUid()), + eq(TEST_VPN_PKG)); + } + + @Test + public void testStartVpnProfileVpnServicePreconsented() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OP_ACTIVATE_VPN); + + when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) + .thenReturn(mVpnProfile.encode()); + + vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore); + + // Verify that the the ACTIVATE_VPN appop was checked, but no error was thrown. + verify(mAppOps).noteOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN, Process.myUid(), TEST_VPN_PKG); + } + + @Test + public void testStartVpnProfileNotConsented() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(); + + try { + vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore); + fail("Expected failure due to no user consent"); + } catch (SecurityException expected) { + } + + // Verify both appops were checked. + verify(mAppOps) + .noteOpNoThrow( + eq(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN), + eq(Process.myUid()), + eq(TEST_VPN_PKG)); + verify(mAppOps).noteOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN, Process.myUid(), TEST_VPN_PKG); + + // Keystore should never have been accessed. + verify(mKeyStore, never()).get(any()); + } + + @Test + public void testStartVpnProfileMissingProfile() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + + when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))).thenReturn(null); + + try { + vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore); + fail("Expected failure due to missing profile"); + } catch (IllegalArgumentException expected) { + } + + verify(mKeyStore).get(vpn.getProfileNameForPackage(TEST_VPN_PKG)); + verify(mAppOps) + .noteOpNoThrow( + eq(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN), + eq(Process.myUid()), + eq(TEST_VPN_PKG)); + } + + @Test + public void testStartVpnProfileRestrictedUser() throws Exception { + final Vpn vpn = + createVpnAndSetupUidChecks( + restrictedProfileA, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + + try { + vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore); + fail("Expected SecurityException due to restricted user"); + } catch (SecurityException expected) { + } + } + + @Test + public void testStopVpnProfileRestrictedUser() throws Exception { + final Vpn vpn = + createVpnAndSetupUidChecks( + restrictedProfileA, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + + try { + vpn.stopVpnProfile(TEST_VPN_PKG); + fail("Expected SecurityException due to restricted user"); + } catch (SecurityException expected) { + } + } + + @Test + public void testSetPackageAuthorizationVpnService() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(); + + assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_SERVICE)); + verify(mAppOps) + .setMode( + eq(AppOpsManager.OP_ACTIVATE_VPN), + eq(Process.myUid()), + eq(TEST_VPN_PKG), + eq(AppOpsManager.MODE_ALLOWED)); + } + + @Test + public void testSetPackageAuthorizationPlatformVpn() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(); + + assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_PLATFORM)); + verify(mAppOps) + .setMode( + eq(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN), + eq(Process.myUid()), + eq(TEST_VPN_PKG), + eq(AppOpsManager.MODE_ALLOWED)); + } + + @Test + public void testSetPackageAuthorizationRevokeAuthorization() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(); + + assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_NONE)); + verify(mAppOps) + .setMode( + eq(AppOpsManager.OP_ACTIVATE_VPN), + eq(Process.myUid()), + eq(TEST_VPN_PKG), + eq(AppOpsManager.MODE_IGNORED)); + verify(mAppOps) + .setMode( + eq(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN), + eq(Process.myUid()), + eq(TEST_VPN_PKG), + eq(AppOpsManager.MODE_IGNORED)); + } + + private void setAndVerifyAlwaysOnPackage(Vpn vpn, int uid, boolean lockdownEnabled) { + assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, lockdownEnabled, null, mKeyStore)); + + verify(mKeyStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); + verify(mAppOps).setMode( + eq(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN), eq(uid), eq(TEST_VPN_PKG), + eq(AppOpsManager.MODE_ALLOWED)); + + verify(mSystemServices).settingsSecurePutStringForUser( + eq(Settings.Secure.ALWAYS_ON_VPN_APP), eq(TEST_VPN_PKG), eq(primaryUser.id)); + verify(mSystemServices).settingsSecurePutIntForUser( + eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN), eq(lockdownEnabled ? 1 : 0), + eq(primaryUser.id)); + verify(mSystemServices).settingsSecurePutStringForUser( + eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST), eq(""), eq(primaryUser.id)); + } + + @Test + public void testSetAndStartAlwaysOnVpn() throws Exception { + final Vpn vpn = createVpn(primaryUser.id); + setMockedUsers(primaryUser); + + // UID checks must return a different UID; otherwise it'll be treated as already prepared. + final int uid = Process.myUid() + 1; + when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt())) + .thenReturn(uid); + when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) + .thenReturn(mVpnProfile.encode()); + + setAndVerifyAlwaysOnPackage(vpn, uid, false); + assertTrue(vpn.startAlwaysOnVpn(mKeyStore)); + + // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in + // a subsequent CL. + } + + @Test + public void testStartLegacyVpn() throws Exception { + final Vpn vpn = createVpn(primaryUser.id); + setMockedUsers(primaryUser); + + // Dummy egress interface + final String egressIface = "DUMMY0"; + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(egressIface); + + final RouteInfo defaultRoute = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), + InetAddresses.parseNumericAddress("192.0.2.0"), egressIface); + lp.addRoute(defaultRoute); + + vpn.startLegacyVpn(mVpnProfile, mKeyStore, lp); + + // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in + // a subsequent CL. } /** * Mock some methods of vpn object. */ private Vpn createVpn(@UserIdInt int userId) { - return new Vpn(Looper.myLooper(), mContext, mNetService, userId, mSystemServices); + return new Vpn(Looper.myLooper(), mContext, mNetService, + userId, mKeyStore, mSystemServices, mIkev2SessionCreator); } private static void assertBlocked(Vpn vpn, int... uids) { diff --git a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java deleted file mode 100644 index 2b2e8a72ab04..000000000000 --- a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java +++ /dev/null @@ -1,501 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.connectivity.tethering; - -import static android.net.ConnectivityManager.TETHERING_BLUETOOTH; -import static android.net.ConnectivityManager.TETHERING_USB; -import static android.net.ConnectivityManager.TETHERING_WIFI; -import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN; -import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; -import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED; -import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.res.Resources; -import android.net.util.SharedLog; -import android.os.Bundle; -import android.os.Message; -import android.os.PersistableBundle; -import android.os.ResultReceiver; -import android.os.test.TestLooper; -import android.provider.Settings; -import android.telephony.CarrierConfigManager; -import android.test.mock.MockContentResolver; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.internal.R; -import com.android.internal.util.State; -import com.android.internal.util.StateMachine; -import com.android.internal.util.test.BroadcastInterceptingContext; -import com.android.internal.util.test.FakeSettingsProvider; -import com.android.server.connectivity.MockableSystemProperties; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.ArrayList; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public final class EntitlementManagerTest { - - private static final int EVENT_EM_UPDATE = 1; - private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; - private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app"; - - @Mock private CarrierConfigManager mCarrierConfigManager; - @Mock private Context mContext; - @Mock private MockableSystemProperties mSystemProperties; - @Mock private Resources mResources; - @Mock private SharedLog mLog; - @Mock private EntitlementManager.OnUiEntitlementFailedListener mEntitlementFailedListener; - - // Like so many Android system APIs, these cannot be mocked because it is marked final. - // We have to use the real versions. - private final PersistableBundle mCarrierConfig = new PersistableBundle(); - private final TestLooper mLooper = new TestLooper(); - private Context mMockContext; - private MockContentResolver mContentResolver; - - private TestStateMachine mSM; - private WrappedEntitlementManager mEnMgr; - private TetheringConfiguration mConfig; - - private class MockContext extends BroadcastInterceptingContext { - MockContext(Context base) { - super(base); - } - - @Override - public Resources getResources() { - return mResources; - } - - @Override - public ContentResolver getContentResolver() { - return mContentResolver; - } - } - - public class WrappedEntitlementManager extends EntitlementManager { - public int fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKONWN; - public int uiProvisionCount = 0; - public int silentProvisionCount = 0; - - public WrappedEntitlementManager(Context ctx, StateMachine target, - SharedLog log, int what, MockableSystemProperties systemProperties) { - super(ctx, target, log, what, systemProperties); - } - - public void reset() { - fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKONWN; - uiProvisionCount = 0; - silentProvisionCount = 0; - } - - @Override - protected void runUiTetherProvisioning(int type, int subId, ResultReceiver receiver) { - uiProvisionCount++; - receiver.send(fakeEntitlementResult, null); - } - - @Override - protected void runSilentTetherProvisioning(int type, int subId) { - silentProvisionCount++; - addDownstreamMapping(type, fakeEntitlementResult); - } - } - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - when(mResources.getStringArray(R.array.config_tether_dhcp_range)) - .thenReturn(new String[0]); - when(mResources.getStringArray(R.array.config_tether_usb_regexs)) - .thenReturn(new String[0]); - when(mResources.getStringArray(R.array.config_tether_wifi_regexs)) - .thenReturn(new String[0]); - when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs)) - .thenReturn(new String[0]); - when(mResources.getIntArray(R.array.config_tether_upstream_types)) - .thenReturn(new int[0]); - when(mLog.forSubComponent(anyString())).thenReturn(mLog); - - mContentResolver = new MockContentResolver(); - mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); - mMockContext = new MockContext(mContext); - mSM = new TestStateMachine(); - mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, EVENT_EM_UPDATE, - mSystemProperties); - mEnMgr.setOnUiEntitlementFailedListener(mEntitlementFailedListener); - mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - mEnMgr.setTetheringConfigurationFetcher(() -> { - return mConfig; - }); - } - - @After - public void tearDown() throws Exception { - if (mSM != null) { - mSM.quit(); - mSM = null; - } - } - - private void setupForRequiredProvisioning() { - // Produce some acceptable looking provision app setting if requested. - when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) - .thenReturn(PROVISIONING_APP_NAME); - when(mResources.getString(R.string.config_mobile_hotspot_provision_app_no_ui)) - .thenReturn(PROVISIONING_NO_UI_APP_NAME); - // Don't disable tethering provisioning unless requested. - when(mSystemProperties.getBoolean(eq(EntitlementManager.DISABLE_PROVISIONING_SYSPROP_KEY), - anyBoolean())).thenReturn(false); - // Act like the CarrierConfigManager is present and ready unless told otherwise. - when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE)) - .thenReturn(mCarrierConfigManager); - when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(mCarrierConfig); - mCarrierConfig.putBoolean(CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, true); - mCarrierConfig.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true); - mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - } - - @Test - public void canRequireProvisioning() { - setupForRequiredProvisioning(); - assertTrue(mEnMgr.isTetherProvisioningRequired(mConfig)); - } - - @Test - public void toleratesCarrierConfigManagerMissing() { - setupForRequiredProvisioning(); - when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE)) - .thenReturn(null); - mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - // Couldn't get the CarrierConfigManager, but still had a declared provisioning app. - // Therefore provisioning still be required. - assertTrue(mEnMgr.isTetherProvisioningRequired(mConfig)); - } - - @Test - public void toleratesCarrierConfigMissing() { - setupForRequiredProvisioning(); - when(mCarrierConfigManager.getConfig()).thenReturn(null); - mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - // We still have a provisioning app configured, so still require provisioning. - assertTrue(mEnMgr.isTetherProvisioningRequired(mConfig)); - } - - @Test - public void toleratesCarrierConfigNotLoaded() { - setupForRequiredProvisioning(); - mCarrierConfig.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, false); - // We still have a provisioning app configured, so still require provisioning. - assertTrue(mEnMgr.isTetherProvisioningRequired(mConfig)); - } - - @Test - public void provisioningNotRequiredWhenAppNotFound() { - setupForRequiredProvisioning(); - when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) - .thenReturn(null); - mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - assertFalse(mEnMgr.isTetherProvisioningRequired(mConfig)); - when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) - .thenReturn(new String[] {"malformedApp"}); - mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - assertFalse(mEnMgr.isTetherProvisioningRequired(mConfig)); - } - - @Test - public void testGetLastEntitlementCacheValue() throws Exception { - final CountDownLatch mCallbacklatch = new CountDownLatch(1); - // 1. Entitlement check is not required. - mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - ResultReceiver receiver = new ResultReceiver(null) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - assertEquals(TETHER_ERROR_NO_ERROR, resultCode); - mCallbacklatch.countDown(); - } - }; - mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); - mLooper.dispatchAll(); - callbackTimeoutHelper(mCallbacklatch); - assertEquals(0, mEnMgr.uiProvisionCount); - mEnMgr.reset(); - - setupForRequiredProvisioning(); - // 2. No cache value and don't need to run entitlement check. - receiver = new ResultReceiver(null) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - assertEquals(TETHER_ERROR_ENTITLEMENT_UNKONWN, resultCode); - mCallbacklatch.countDown(); - } - }; - mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false); - mLooper.dispatchAll(); - callbackTimeoutHelper(mCallbacklatch); - assertEquals(0, mEnMgr.uiProvisionCount); - mEnMgr.reset(); - // 3. No cache value and ui entitlement check is needed. - mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; - receiver = new ResultReceiver(null) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - assertEquals(TETHER_ERROR_PROVISION_FAILED, resultCode); - mCallbacklatch.countDown(); - } - }; - mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); - mLooper.dispatchAll(); - callbackTimeoutHelper(mCallbacklatch); - assertEquals(1, mEnMgr.uiProvisionCount); - mEnMgr.reset(); - // 4. Cache value is TETHER_ERROR_PROVISION_FAILED and don't need to run entitlement check. - mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - receiver = new ResultReceiver(null) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - assertEquals(TETHER_ERROR_PROVISION_FAILED, resultCode); - mCallbacklatch.countDown(); - } - }; - mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false); - mLooper.dispatchAll(); - callbackTimeoutHelper(mCallbacklatch); - assertEquals(0, mEnMgr.uiProvisionCount); - mEnMgr.reset(); - // 5. Cache value is TETHER_ERROR_PROVISION_FAILED and ui entitlement check is needed. - mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - receiver = new ResultReceiver(null) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - assertEquals(TETHER_ERROR_NO_ERROR, resultCode); - mCallbacklatch.countDown(); - } - }; - mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); - mLooper.dispatchAll(); - callbackTimeoutHelper(mCallbacklatch); - assertEquals(1, mEnMgr.uiProvisionCount); - mEnMgr.reset(); - // 6. Cache value is TETHER_ERROR_NO_ERROR. - mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - receiver = new ResultReceiver(null) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - assertEquals(TETHER_ERROR_NO_ERROR, resultCode); - mCallbacklatch.countDown(); - } - }; - mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); - mLooper.dispatchAll(); - callbackTimeoutHelper(mCallbacklatch); - assertEquals(0, mEnMgr.uiProvisionCount); - mEnMgr.reset(); - // 7. Test get value for other downstream type. - receiver = new ResultReceiver(null) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - assertEquals(TETHER_ERROR_ENTITLEMENT_UNKONWN, resultCode); - mCallbacklatch.countDown(); - } - }; - mEnMgr.getLatestTetheringEntitlementResult(TETHERING_USB, receiver, false); - mLooper.dispatchAll(); - callbackTimeoutHelper(mCallbacklatch); - assertEquals(0, mEnMgr.uiProvisionCount); - mEnMgr.reset(); - } - - void callbackTimeoutHelper(final CountDownLatch latch) throws Exception { - if (!latch.await(1, TimeUnit.SECONDS)) { - fail("Timout, fail to receive callback"); - } - } - - @Test - public void verifyPermissionResult() { - setupForRequiredProvisioning(); - mEnMgr.notifyUpstream(true); - mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; - mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); - mLooper.dispatchAll(); - assertFalse(mEnMgr.isCellularUpstreamPermitted()); - mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI); - mLooper.dispatchAll(); - mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); - mLooper.dispatchAll(); - assertTrue(mEnMgr.isCellularUpstreamPermitted()); - } - - @Test - public void verifyPermissionIfAllNotApproved() { - setupForRequiredProvisioning(); - mEnMgr.notifyUpstream(true); - mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; - mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); - mLooper.dispatchAll(); - assertFalse(mEnMgr.isCellularUpstreamPermitted()); - mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; - mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true); - mLooper.dispatchAll(); - assertFalse(mEnMgr.isCellularUpstreamPermitted()); - mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; - mEnMgr.startProvisioningIfNeeded(TETHERING_BLUETOOTH, true); - mLooper.dispatchAll(); - assertFalse(mEnMgr.isCellularUpstreamPermitted()); - } - - @Test - public void verifyPermissionIfAnyApproved() { - setupForRequiredProvisioning(); - mEnMgr.notifyUpstream(true); - mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); - mLooper.dispatchAll(); - assertTrue(mEnMgr.isCellularUpstreamPermitted()); - mLooper.dispatchAll(); - mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; - mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true); - mLooper.dispatchAll(); - assertTrue(mEnMgr.isCellularUpstreamPermitted()); - mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI); - mLooper.dispatchAll(); - assertFalse(mEnMgr.isCellularUpstreamPermitted()); - - } - - @Test - public void testRunTetherProvisioning() { - setupForRequiredProvisioning(); - // 1. start ui provisioning, upstream is mobile - mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - mEnMgr.notifyUpstream(true); - mLooper.dispatchAll(); - mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true); - mLooper.dispatchAll(); - assertEquals(1, mEnMgr.uiProvisionCount); - assertEquals(0, mEnMgr.silentProvisionCount); - assertTrue(mEnMgr.isCellularUpstreamPermitted()); - mEnMgr.reset(); - // 2. start no-ui provisioning - mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, false); - mLooper.dispatchAll(); - assertEquals(0, mEnMgr.uiProvisionCount); - assertEquals(1, mEnMgr.silentProvisionCount); - assertTrue(mEnMgr.isCellularUpstreamPermitted()); - mEnMgr.reset(); - // 3. tear down mobile, then start ui provisioning - mEnMgr.notifyUpstream(false); - mLooper.dispatchAll(); - mEnMgr.startProvisioningIfNeeded(TETHERING_BLUETOOTH, true); - mLooper.dispatchAll(); - assertEquals(0, mEnMgr.uiProvisionCount); - assertEquals(0, mEnMgr.silentProvisionCount); - mEnMgr.reset(); - // 4. switch upstream back to mobile - mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - mEnMgr.notifyUpstream(true); - mLooper.dispatchAll(); - assertEquals(1, mEnMgr.uiProvisionCount); - assertEquals(0, mEnMgr.silentProvisionCount); - assertTrue(mEnMgr.isCellularUpstreamPermitted()); - mEnMgr.reset(); - // 5. tear down mobile, then switch SIM - mEnMgr.notifyUpstream(false); - mLooper.dispatchAll(); - mEnMgr.reevaluateSimCardProvisioning(mConfig); - assertEquals(0, mEnMgr.uiProvisionCount); - assertEquals(0, mEnMgr.silentProvisionCount); - mEnMgr.reset(); - // 6. switch upstream back to mobile again - mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; - mEnMgr.notifyUpstream(true); - mLooper.dispatchAll(); - assertEquals(0, mEnMgr.uiProvisionCount); - assertEquals(3, mEnMgr.silentProvisionCount); - mEnMgr.reset(); - } - - @Test - public void testCallStopTetheringWhenUiProvisioningFail() { - setupForRequiredProvisioning(); - verify(mEntitlementFailedListener, times(0)).onUiEntitlementFailed(TETHERING_WIFI); - mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; - mEnMgr.notifyUpstream(true); - mLooper.dispatchAll(); - mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); - mLooper.dispatchAll(); - assertEquals(1, mEnMgr.uiProvisionCount); - verify(mEntitlementFailedListener, times(1)).onUiEntitlementFailed(TETHERING_WIFI); - } - - public class TestStateMachine extends StateMachine { - public final ArrayList<Message> messages = new ArrayList<>(); - private final State - mLoggingState = new EntitlementManagerTest.TestStateMachine.LoggingState(); - - class LoggingState extends State { - @Override public void enter() { - messages.clear(); - } - - @Override public void exit() { - messages.clear(); - } - - @Override public boolean processMessage(Message msg) { - messages.add(msg); - return false; - } - } - - public TestStateMachine() { - super("EntitlementManagerTest.TestStateMachine", mLooper.getLooper()); - addState(mLoggingState); - setInitialState(mLoggingState); - super.start(); - } - } -} diff --git a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java deleted file mode 100644 index be54b1a93b5d..000000000000 --- a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java +++ /dev/null @@ -1,739 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.connectivity.tethering; - -import static android.net.NetworkStats.SET_DEFAULT; -import static android.net.NetworkStats.STATS_PER_IFACE; -import static android.net.NetworkStats.STATS_PER_UID; -import static android.net.NetworkStats.TAG_NONE; -import static android.net.NetworkStats.UID_ALL; -import static android.net.TrafficStats.UID_TETHERING; -import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED; - -import static com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.net.ITetheringStatsProvider; -import android.net.IpPrefix; -import android.net.LinkAddress; -import android.net.LinkProperties; -import android.net.NetworkStats; -import android.net.RouteInfo; -import android.net.util.SharedLog; -import android.os.ConditionVariable; -import android.os.Handler; -import android.os.INetworkManagementService; -import android.os.Looper; -import android.provider.Settings; -import android.provider.Settings.SettingNotFoundException; -import android.test.mock.MockContentResolver; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.internal.util.test.FakeSettingsProvider; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.net.InetAddress; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Set; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class OffloadControllerTest { - private static final String RNDIS0 = "test_rndis0"; - private static final String RMNET0 = "test_rmnet_data0"; - private static final String WLAN0 = "test_wlan0"; - - private static final String IPV6_LINKLOCAL = "fe80::/64"; - private static final String IPV6_DOC_PREFIX = "2001:db8::/64"; - private static final String IPV6_DISCARD_PREFIX = "100::/64"; - private static final String USB_PREFIX = "192.168.42.0/24"; - private static final String WIFI_PREFIX = "192.168.43.0/24"; - - @Mock private OffloadHardwareInterface mHardware; - @Mock private ApplicationInfo mApplicationInfo; - @Mock private Context mContext; - @Mock private INetworkManagementService mNMService; - private final ArgumentCaptor<ArrayList> mStringArrayCaptor = - ArgumentCaptor.forClass(ArrayList.class); - private final ArgumentCaptor<ITetheringStatsProvider.Stub> mTetherStatsProviderCaptor = - ArgumentCaptor.forClass(ITetheringStatsProvider.Stub.class); - private final ArgumentCaptor<OffloadHardwareInterface.ControlCallback> mControlCallbackCaptor = - ArgumentCaptor.forClass(OffloadHardwareInterface.ControlCallback.class); - private MockContentResolver mContentResolver; - - @Before public void setUp() { - MockitoAnnotations.initMocks(this); - when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo); - when(mContext.getPackageName()).thenReturn("OffloadControllerTest"); - mContentResolver = new MockContentResolver(mContext); - mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); - when(mContext.getContentResolver()).thenReturn(mContentResolver); - FakeSettingsProvider.clearSettingsProvider(); - } - - @After public void tearDown() throws Exception { - FakeSettingsProvider.clearSettingsProvider(); - } - - private void setupFunctioningHardwareInterface() { - when(mHardware.initOffloadConfig()).thenReturn(true); - when(mHardware.initOffloadControl(mControlCallbackCaptor.capture())) - .thenReturn(true); - when(mHardware.setUpstreamParameters(anyString(), any(), any(), any())).thenReturn(true); - when(mHardware.getForwardedStats(any())).thenReturn(new ForwardedStats()); - when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(true); - } - - private void enableOffload() { - Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0); - } - - private void waitForIdle() { - ConditionVariable cv = new ConditionVariable(); - new Handler(Looper.getMainLooper()).post(() -> { cv.open(); }); - cv.block(); - } - - private OffloadController makeOffloadController() throws Exception { - OffloadController offload = new OffloadController(new Handler(Looper.getMainLooper()), - mHardware, mContentResolver, mNMService, new SharedLog("test")); - verify(mNMService).registerTetheringStatsProvider( - mTetherStatsProviderCaptor.capture(), anyString()); - return offload; - } - - @Test - public void testNoSettingsValueDefaultDisabledDoesNotStart() throws Exception { - setupFunctioningHardwareInterface(); - when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(1); - try { - Settings.Global.getInt(mContentResolver, TETHER_OFFLOAD_DISABLED); - fail(); - } catch (SettingNotFoundException expected) {} - - final OffloadController offload = makeOffloadController(); - offload.start(); - - final InOrder inOrder = inOrder(mHardware); - inOrder.verify(mHardware, times(1)).getDefaultTetherOffloadDisabled(); - inOrder.verify(mHardware, never()).initOffloadConfig(); - inOrder.verify(mHardware, never()).initOffloadControl( - any(OffloadHardwareInterface.ControlCallback.class)); - inOrder.verifyNoMoreInteractions(); - } - - @Test - public void testNoSettingsValueDefaultEnabledDoesStart() throws Exception { - setupFunctioningHardwareInterface(); - when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(0); - try { - Settings.Global.getInt(mContentResolver, TETHER_OFFLOAD_DISABLED); - fail(); - } catch (SettingNotFoundException expected) {} - - final OffloadController offload = makeOffloadController(); - offload.start(); - - final InOrder inOrder = inOrder(mHardware); - inOrder.verify(mHardware, times(1)).getDefaultTetherOffloadDisabled(); - inOrder.verify(mHardware, times(1)).initOffloadConfig(); - inOrder.verify(mHardware, times(1)).initOffloadControl( - any(OffloadHardwareInterface.ControlCallback.class)); - inOrder.verifyNoMoreInteractions(); - } - - @Test - public void testSettingsAllowsStart() throws Exception { - setupFunctioningHardwareInterface(); - Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0); - - final OffloadController offload = makeOffloadController(); - offload.start(); - - final InOrder inOrder = inOrder(mHardware); - inOrder.verify(mHardware, times(1)).getDefaultTetherOffloadDisabled(); - inOrder.verify(mHardware, times(1)).initOffloadConfig(); - inOrder.verify(mHardware, times(1)).initOffloadControl( - any(OffloadHardwareInterface.ControlCallback.class)); - inOrder.verifyNoMoreInteractions(); - } - - @Test - public void testSettingsDisablesStart() throws Exception { - setupFunctioningHardwareInterface(); - Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 1); - - final OffloadController offload = makeOffloadController(); - offload.start(); - - final InOrder inOrder = inOrder(mHardware); - inOrder.verify(mHardware, times(1)).getDefaultTetherOffloadDisabled(); - inOrder.verify(mHardware, never()).initOffloadConfig(); - inOrder.verify(mHardware, never()).initOffloadControl(anyObject()); - inOrder.verifyNoMoreInteractions(); - } - - @Test - public void testSetUpstreamLinkPropertiesWorking() throws Exception { - setupFunctioningHardwareInterface(); - enableOffload(); - - final OffloadController offload = makeOffloadController(); - offload.start(); - - final InOrder inOrder = inOrder(mHardware); - inOrder.verify(mHardware, times(1)).getDefaultTetherOffloadDisabled(); - inOrder.verify(mHardware, times(1)).initOffloadConfig(); - inOrder.verify(mHardware, times(1)).initOffloadControl( - any(OffloadHardwareInterface.ControlCallback.class)); - inOrder.verifyNoMoreInteractions(); - - // In reality, the UpstreamNetworkMonitor would have passed down to us - // a covering set of local prefixes representing a minimum essential - // set plus all the prefixes on networks with network agents. - // - // We simulate that there, and then add upstream elements one by one - // and watch what happens. - final Set<IpPrefix> minimumLocalPrefixes = new HashSet<>(); - for (String s : new String[]{ - "127.0.0.0/8", "192.0.2.0/24", "fe80::/64", "2001:db8::/64"}) { - minimumLocalPrefixes.add(new IpPrefix(s)); - } - offload.setLocalPrefixes(minimumLocalPrefixes); - inOrder.verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture()); - ArrayList<String> localPrefixes = mStringArrayCaptor.getValue(); - assertEquals(4, localPrefixes.size()); - assertArrayListContains(localPrefixes, - "127.0.0.0/8", "192.0.2.0/24", "fe80::/64", "2001:db8::/64"); - inOrder.verifyNoMoreInteractions(); - - offload.setUpstreamLinkProperties(null); - // No change in local addresses means no call to setLocalPrefixes(). - inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture()); - // This LinkProperties value does not differ from the default upstream. - // There should be no extraneous call to setUpstreamParameters(). - inOrder.verify(mHardware, never()).setUpstreamParameters( - anyObject(), anyObject(), anyObject(), anyObject()); - inOrder.verifyNoMoreInteractions(); - - final LinkProperties lp = new LinkProperties(); - - final String testIfName = "rmnet_data17"; - lp.setInterfaceName(testIfName); - offload.setUpstreamLinkProperties(lp); - // No change in local addresses means no call to setLocalPrefixes(). - inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture()); - inOrder.verify(mHardware, times(1)).setUpstreamParameters( - eq(testIfName), eq(null), eq(null), eq(null)); - inOrder.verify(mHardware, times(1)).setDataLimit(eq(testIfName), eq(Long.MAX_VALUE)); - inOrder.verifyNoMoreInteractions(); - - final String ipv4Addr = "192.0.2.5"; - final String linkAddr = ipv4Addr + "/24"; - lp.addLinkAddress(new LinkAddress(linkAddr)); - lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"))); - offload.setUpstreamLinkProperties(lp); - // IPv4 prefixes and addresses on the upstream are simply left as whole - // prefixes (already passed in from UpstreamNetworkMonitor code). If a - // tethering client sends traffic to the IPv4 default router or other - // clients on the upstream this will not be hardware-forwarded, and that - // should be fine for now. Ergo: no change in local addresses, no call - // to setLocalPrefixes(). - inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture()); - inOrder.verify(mHardware, times(1)).setUpstreamParameters( - eq(testIfName), eq(ipv4Addr), eq(null), eq(null)); - inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName)); - inOrder.verify(mHardware, times(1)).setDataLimit(eq(testIfName), eq(Long.MAX_VALUE)); - inOrder.verifyNoMoreInteractions(); - - final String ipv4Gateway = "192.0.2.1"; - lp.addRoute(new RouteInfo(InetAddress.getByName(ipv4Gateway))); - offload.setUpstreamLinkProperties(lp); - // No change in local addresses means no call to setLocalPrefixes(). - inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture()); - inOrder.verify(mHardware, times(1)).setUpstreamParameters( - eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), eq(null)); - inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName)); - inOrder.verify(mHardware, times(1)).setDataLimit(eq(testIfName), eq(Long.MAX_VALUE)); - inOrder.verifyNoMoreInteractions(); - - final String ipv6Gw1 = "fe80::cafe"; - lp.addRoute(new RouteInfo(InetAddress.getByName(ipv6Gw1))); - offload.setUpstreamLinkProperties(lp); - // No change in local addresses means no call to setLocalPrefixes(). - inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture()); - inOrder.verify(mHardware, times(1)).setUpstreamParameters( - eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture()); - inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName)); - ArrayList<String> v6gws = mStringArrayCaptor.getValue(); - assertEquals(1, v6gws.size()); - assertTrue(v6gws.contains(ipv6Gw1)); - inOrder.verify(mHardware, times(1)).setDataLimit(eq(testIfName), eq(Long.MAX_VALUE)); - inOrder.verifyNoMoreInteractions(); - - final String ipv6Gw2 = "fe80::d00d"; - lp.addRoute(new RouteInfo(InetAddress.getByName(ipv6Gw2))); - offload.setUpstreamLinkProperties(lp); - // No change in local addresses means no call to setLocalPrefixes(). - inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture()); - inOrder.verify(mHardware, times(1)).setUpstreamParameters( - eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture()); - inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName)); - v6gws = mStringArrayCaptor.getValue(); - assertEquals(2, v6gws.size()); - assertTrue(v6gws.contains(ipv6Gw1)); - assertTrue(v6gws.contains(ipv6Gw2)); - inOrder.verify(mHardware, times(1)).setDataLimit(eq(testIfName), eq(Long.MAX_VALUE)); - inOrder.verifyNoMoreInteractions(); - - final LinkProperties stacked = new LinkProperties(); - stacked.setInterfaceName("stacked"); - stacked.addLinkAddress(new LinkAddress("192.0.2.129/25")); - stacked.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254"))); - stacked.addRoute(new RouteInfo(InetAddress.getByName("fe80::bad:f00"))); - assertTrue(lp.addStackedLink(stacked)); - offload.setUpstreamLinkProperties(lp); - // No change in local addresses means no call to setLocalPrefixes(). - inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture()); - inOrder.verify(mHardware, times(1)).setUpstreamParameters( - eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture()); - inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName)); - v6gws = mStringArrayCaptor.getValue(); - assertEquals(2, v6gws.size()); - assertTrue(v6gws.contains(ipv6Gw1)); - assertTrue(v6gws.contains(ipv6Gw2)); - inOrder.verify(mHardware, times(1)).setDataLimit(eq(testIfName), eq(Long.MAX_VALUE)); - inOrder.verifyNoMoreInteractions(); - - // Add in some IPv6 upstream info. When there is a tethered downstream - // making use of the IPv6 prefix we would expect to see the /64 route - // removed from "local prefixes" and /128s added for the upstream IPv6 - // addresses. This is not yet implemented, and for now we simply - // expect to see these /128s. - lp.addRoute(new RouteInfo(new IpPrefix("2001:db8::/64"))); - // "2001:db8::/64" plus "assigned" ASCII in hex - lp.addLinkAddress(new LinkAddress("2001:db8::6173:7369:676e:6564/64")); - // "2001:db8::/64" plus "random" ASCII in hex - lp.addLinkAddress(new LinkAddress("2001:db8::7261:6e64:6f6d/64")); - offload.setUpstreamLinkProperties(lp); - inOrder.verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture()); - localPrefixes = mStringArrayCaptor.getValue(); - assertEquals(6, localPrefixes.size()); - assertArrayListContains(localPrefixes, - "127.0.0.0/8", "192.0.2.0/24", "fe80::/64", "2001:db8::/64", - "2001:db8::6173:7369:676e:6564/128", "2001:db8::7261:6e64:6f6d/128"); - // The relevant parts of the LinkProperties have not changed, but at the - // moment we do not de-dup upstream LinkProperties this carefully. - inOrder.verify(mHardware, times(1)).setUpstreamParameters( - eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture()); - v6gws = mStringArrayCaptor.getValue(); - assertEquals(2, v6gws.size()); - assertTrue(v6gws.contains(ipv6Gw1)); - assertTrue(v6gws.contains(ipv6Gw2)); - inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName)); - inOrder.verify(mHardware, times(1)).setDataLimit(eq(testIfName), eq(Long.MAX_VALUE)); - inOrder.verifyNoMoreInteractions(); - - // Completely identical LinkProperties updates are de-duped. - offload.setUpstreamLinkProperties(lp); - // This LinkProperties value does not differ from the default upstream. - // There should be no extraneous call to setUpstreamParameters(). - inOrder.verify(mHardware, never()).setUpstreamParameters( - anyObject(), anyObject(), anyObject(), anyObject()); - inOrder.verifyNoMoreInteractions(); - } - - private void assertNetworkStats(String iface, ForwardedStats stats, NetworkStats.Entry entry) { - assertEquals(iface, entry.iface); - assertEquals(stats.rxBytes, entry.rxBytes); - assertEquals(stats.txBytes, entry.txBytes); - assertEquals(SET_DEFAULT, entry.set); - assertEquals(TAG_NONE, entry.tag); - } - - @Test - public void testGetForwardedStats() throws Exception { - setupFunctioningHardwareInterface(); - enableOffload(); - - final OffloadController offload = makeOffloadController(); - offload.start(); - - final String ethernetIface = "eth1"; - final String mobileIface = "rmnet_data0"; - - ForwardedStats ethernetStats = new ForwardedStats(); - ethernetStats.rxBytes = 12345; - ethernetStats.txBytes = 54321; - - ForwardedStats mobileStats = new ForwardedStats(); - mobileStats.rxBytes = 999; - mobileStats.txBytes = 99999; - - when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats); - when(mHardware.getForwardedStats(eq(mobileIface))).thenReturn(mobileStats); - - InOrder inOrder = inOrder(mHardware); - - final LinkProperties lp = new LinkProperties(); - lp.setInterfaceName(ethernetIface); - offload.setUpstreamLinkProperties(lp); - // Previous upstream was null, so no stats are fetched. - inOrder.verify(mHardware, never()).getForwardedStats(any()); - - lp.setInterfaceName(mobileIface); - offload.setUpstreamLinkProperties(lp); - // Expect that we fetch stats from the previous upstream. - inOrder.verify(mHardware, times(1)).getForwardedStats(eq(ethernetIface)); - - lp.setInterfaceName(ethernetIface); - offload.setUpstreamLinkProperties(lp); - // Expect that we fetch stats from the previous upstream. - inOrder.verify(mHardware, times(1)).getForwardedStats(eq(mobileIface)); - - ethernetStats = new ForwardedStats(); - ethernetStats.rxBytes = 100000; - ethernetStats.txBytes = 100000; - when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats); - offload.setUpstreamLinkProperties(null); - // Expect that we first clear the HAL's upstream parameters. - inOrder.verify(mHardware, times(1)).setUpstreamParameters( - eq(""), eq("0.0.0.0"), eq("0.0.0.0"), eq(null)); - // Expect that we fetch stats from the previous upstream. - inOrder.verify(mHardware, times(1)).getForwardedStats(eq(ethernetIface)); - - ITetheringStatsProvider provider = mTetherStatsProviderCaptor.getValue(); - NetworkStats stats = provider.getTetherStats(STATS_PER_IFACE); - NetworkStats perUidStats = provider.getTetherStats(STATS_PER_UID); - waitForIdle(); - // There is no current upstream, so no stats are fetched. - inOrder.verify(mHardware, never()).getForwardedStats(any()); - inOrder.verifyNoMoreInteractions(); - - assertEquals(2, stats.size()); - assertEquals(2, perUidStats.size()); - - NetworkStats.Entry entry = null; - for (int i = 0; i < stats.size(); i++) { - assertEquals(UID_ALL, stats.getValues(i, entry).uid); - assertEquals(UID_TETHERING, perUidStats.getValues(i, entry).uid); - } - - int ethernetPosition = ethernetIface.equals(stats.getValues(0, entry).iface) ? 0 : 1; - int mobilePosition = 1 - ethernetPosition; - - entry = stats.getValues(mobilePosition, entry); - assertNetworkStats(mobileIface, mobileStats, entry); - entry = perUidStats.getValues(mobilePosition, entry); - assertNetworkStats(mobileIface, mobileStats, entry); - - ethernetStats.rxBytes = 12345 + 100000; - ethernetStats.txBytes = 54321 + 100000; - entry = stats.getValues(ethernetPosition, entry); - assertNetworkStats(ethernetIface, ethernetStats, entry); - entry = perUidStats.getValues(ethernetPosition, entry); - assertNetworkStats(ethernetIface, ethernetStats, entry); - } - - @Test - public void testSetInterfaceQuota() throws Exception { - setupFunctioningHardwareInterface(); - enableOffload(); - - final OffloadController offload = makeOffloadController(); - offload.start(); - - final String ethernetIface = "eth1"; - final String mobileIface = "rmnet_data0"; - final long ethernetLimit = 12345; - final long mobileLimit = 12345678; - - final LinkProperties lp = new LinkProperties(); - lp.setInterfaceName(ethernetIface); - offload.setUpstreamLinkProperties(lp); - - ITetheringStatsProvider provider = mTetherStatsProviderCaptor.getValue(); - final InOrder inOrder = inOrder(mHardware); - when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(true); - when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(true); - - // Applying an interface quota to the current upstream immediately sends it to the hardware. - provider.setInterfaceQuota(ethernetIface, ethernetLimit); - waitForIdle(); - inOrder.verify(mHardware).setDataLimit(ethernetIface, ethernetLimit); - inOrder.verifyNoMoreInteractions(); - - // Applying an interface quota to another upstream does not take any immediate action. - provider.setInterfaceQuota(mobileIface, mobileLimit); - waitForIdle(); - inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong()); - - // Switching to that upstream causes the quota to be applied if the parameters were applied - // correctly. - lp.setInterfaceName(mobileIface); - offload.setUpstreamLinkProperties(lp); - waitForIdle(); - inOrder.verify(mHardware).setDataLimit(mobileIface, mobileLimit); - - // Setting a limit of ITetheringStatsProvider.QUOTA_UNLIMITED causes the limit to be set - // to Long.MAX_VALUE. - provider.setInterfaceQuota(mobileIface, ITetheringStatsProvider.QUOTA_UNLIMITED); - waitForIdle(); - inOrder.verify(mHardware).setDataLimit(mobileIface, Long.MAX_VALUE); - - // If setting upstream parameters fails, then the data limit is not set. - when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(false); - lp.setInterfaceName(ethernetIface); - offload.setUpstreamLinkProperties(lp); - provider.setInterfaceQuota(mobileIface, mobileLimit); - waitForIdle(); - inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong()); - - // If setting the data limit fails while changing upstreams, offload is stopped. - when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(true); - when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(false); - lp.setInterfaceName(mobileIface); - offload.setUpstreamLinkProperties(lp); - provider.setInterfaceQuota(mobileIface, mobileLimit); - waitForIdle(); - inOrder.verify(mHardware).getForwardedStats(ethernetIface); - inOrder.verify(mHardware).stopOffloadControl(); - } - - @Test - public void testDataLimitCallback() throws Exception { - setupFunctioningHardwareInterface(); - enableOffload(); - - final OffloadController offload = makeOffloadController(); - offload.start(); - - OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue(); - callback.onStoppedLimitReached(); - verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue()); - } - - @Test - public void testAddRemoveDownstreams() throws Exception { - setupFunctioningHardwareInterface(); - enableOffload(); - - final OffloadController offload = makeOffloadController(); - offload.start(); - - final InOrder inOrder = inOrder(mHardware); - inOrder.verify(mHardware, times(1)).initOffloadConfig(); - inOrder.verify(mHardware, times(1)).initOffloadControl( - any(OffloadHardwareInterface.ControlCallback.class)); - inOrder.verifyNoMoreInteractions(); - - // Tethering makes several calls to setLocalPrefixes() before add/remove - // downstream calls are made. This is not tested here; only the behavior - // of notifyDownstreamLinkProperties() and removeDownstreamInterface() - // are tested. - - // [1] USB tethering is started. - final LinkProperties usbLinkProperties = new LinkProperties(); - usbLinkProperties.setInterfaceName(RNDIS0); - usbLinkProperties.addLinkAddress(new LinkAddress("192.168.42.1/24")); - usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(USB_PREFIX))); - offload.notifyDownstreamLinkProperties(usbLinkProperties); - inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, USB_PREFIX); - inOrder.verifyNoMoreInteractions(); - - // [2] Routes for IPv6 link-local prefixes should never be added. - usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_LINKLOCAL))); - offload.notifyDownstreamLinkProperties(usbLinkProperties); - inOrder.verify(mHardware, never()).addDownstreamPrefix(eq(RNDIS0), anyString()); - inOrder.verifyNoMoreInteractions(); - - // [3] Add an IPv6 prefix for good measure. Only new offload-able - // prefixes should be passed to the HAL. - usbLinkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64")); - usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX))); - offload.notifyDownstreamLinkProperties(usbLinkProperties); - inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, IPV6_DOC_PREFIX); - inOrder.verifyNoMoreInteractions(); - - // [4] Adding addresses doesn't affect notifyDownstreamLinkProperties(). - // The address is passed in by a separate setLocalPrefixes() invocation. - usbLinkProperties.addLinkAddress(new LinkAddress("2001:db8::2/64")); - offload.notifyDownstreamLinkProperties(usbLinkProperties); - inOrder.verify(mHardware, never()).addDownstreamPrefix(eq(RNDIS0), anyString()); - - // [5] Differences in local routes are converted into addDownstream() - // and removeDownstream() invocations accordingly. - usbLinkProperties.removeRoute(new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX), null, RNDIS0)); - usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_DISCARD_PREFIX))); - offload.notifyDownstreamLinkProperties(usbLinkProperties); - inOrder.verify(mHardware, times(1)).removeDownstreamPrefix(RNDIS0, IPV6_DOC_PREFIX); - inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, IPV6_DISCARD_PREFIX); - inOrder.verifyNoMoreInteractions(); - - // [6] Removing a downstream interface which was never added causes no - // interactions with the HAL. - offload.removeDownstreamInterface(WLAN0); - inOrder.verifyNoMoreInteractions(); - - // [7] Removing an active downstream removes all remaining prefixes. - offload.removeDownstreamInterface(RNDIS0); - inOrder.verify(mHardware, times(1)).removeDownstreamPrefix(RNDIS0, USB_PREFIX); - inOrder.verify(mHardware, times(1)).removeDownstreamPrefix(RNDIS0, IPV6_DISCARD_PREFIX); - inOrder.verifyNoMoreInteractions(); - } - - @Test - public void testControlCallbackOnStoppedUnsupportedFetchesAllStats() throws Exception { - setupFunctioningHardwareInterface(); - enableOffload(); - - final OffloadController offload = makeOffloadController(); - offload.start(); - - // Pretend to set a few different upstreams (only the interface name - // matters for this test; we're ignoring IP and route information). - final LinkProperties upstreamLp = new LinkProperties(); - for (String ifname : new String[]{RMNET0, WLAN0, RMNET0}) { - upstreamLp.setInterfaceName(ifname); - offload.setUpstreamLinkProperties(upstreamLp); - } - - // Clear invocation history, especially the getForwardedStats() calls - // that happen with setUpstreamParameters(). - clearInvocations(mHardware); - - OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue(); - callback.onStoppedUnsupported(); - - // Verify forwarded stats behaviour. - verify(mHardware, times(1)).getForwardedStats(eq(RMNET0)); - verify(mHardware, times(1)).getForwardedStats(eq(WLAN0)); - verifyNoMoreInteractions(mHardware); - verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue()); - verifyNoMoreInteractions(mNMService); - } - - @Test - public void testControlCallbackOnSupportAvailableFetchesAllStatsAndPushesAllParameters() - throws Exception { - setupFunctioningHardwareInterface(); - enableOffload(); - - final OffloadController offload = makeOffloadController(); - offload.start(); - - // Pretend to set a few different upstreams (only the interface name - // matters for this test; we're ignoring IP and route information). - final LinkProperties upstreamLp = new LinkProperties(); - for (String ifname : new String[]{RMNET0, WLAN0, RMNET0}) { - upstreamLp.setInterfaceName(ifname); - offload.setUpstreamLinkProperties(upstreamLp); - } - - // Pretend that some local prefixes and downstreams have been added - // (and removed, for good measure). - final Set<IpPrefix> minimumLocalPrefixes = new HashSet<>(); - for (String s : new String[]{ - "127.0.0.0/8", "192.0.2.0/24", "fe80::/64", "2001:db8::/64"}) { - minimumLocalPrefixes.add(new IpPrefix(s)); - } - offload.setLocalPrefixes(minimumLocalPrefixes); - - final LinkProperties usbLinkProperties = new LinkProperties(); - usbLinkProperties.setInterfaceName(RNDIS0); - usbLinkProperties.addLinkAddress(new LinkAddress("192.168.42.1/24")); - usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(USB_PREFIX))); - offload.notifyDownstreamLinkProperties(usbLinkProperties); - - final LinkProperties wifiLinkProperties = new LinkProperties(); - wifiLinkProperties.setInterfaceName(WLAN0); - wifiLinkProperties.addLinkAddress(new LinkAddress("192.168.43.1/24")); - wifiLinkProperties.addRoute(new RouteInfo(new IpPrefix(WIFI_PREFIX))); - wifiLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_LINKLOCAL))); - // Use a benchmark prefix (RFC 5180 + erratum), since the documentation - // prefix is included in the excluded prefix list. - wifiLinkProperties.addLinkAddress(new LinkAddress("2001:2::1/64")); - wifiLinkProperties.addLinkAddress(new LinkAddress("2001:2::2/64")); - wifiLinkProperties.addRoute(new RouteInfo(new IpPrefix("2001:2::/64"))); - offload.notifyDownstreamLinkProperties(wifiLinkProperties); - - offload.removeDownstreamInterface(RNDIS0); - - // Clear invocation history, especially the getForwardedStats() calls - // that happen with setUpstreamParameters(). - clearInvocations(mHardware); - - OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue(); - callback.onSupportAvailable(); - - // Verify forwarded stats behaviour. - verify(mHardware, times(1)).getForwardedStats(eq(RMNET0)); - verify(mHardware, times(1)).getForwardedStats(eq(WLAN0)); - verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue()); - verifyNoMoreInteractions(mNMService); - - // TODO: verify local prefixes and downstreams are also pushed to the HAL. - verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture()); - ArrayList<String> localPrefixes = mStringArrayCaptor.getValue(); - assertEquals(4, localPrefixes.size()); - assertArrayListContains(localPrefixes, - // TODO: The logic to find and exclude downstream IP prefixes - // is currently in Tethering's OffloadWrapper but must be moved - // into OffloadController proper. After this, also check for: - // "192.168.43.1/32", "2001:2::1/128", "2001:2::2/128" - "127.0.0.0/8", "192.0.2.0/24", "fe80::/64", "2001:db8::/64"); - verify(mHardware, times(1)).addDownstreamPrefix(WLAN0, "192.168.43.0/24"); - verify(mHardware, times(1)).addDownstreamPrefix(WLAN0, "2001:2::/64"); - verify(mHardware, times(1)).setUpstreamParameters(eq(RMNET0), any(), any(), any()); - verify(mHardware, times(1)).setDataLimit(eq(RMNET0), anyLong()); - verifyNoMoreInteractions(mHardware); - } - - private static void assertArrayListContains(ArrayList<String> list, String... elems) { - for (String element : elems) { - assertTrue(element + " not in list", list.contains(element)); - } - } -} diff --git a/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java b/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java deleted file mode 100644 index e28296354d2a..000000000000 --- a/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.connectivity.tethering; - -import static android.net.ConnectivityManager.TYPE_ETHERNET; -import static android.net.ConnectivityManager.TYPE_MOBILE; -import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; -import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; -import static android.net.ConnectivityManager.TYPE_WIFI; -import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER; -import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; - -import static com.android.internal.R.array.config_tether_upstream_types; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Mockito.when; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.res.Resources; -import android.net.util.SharedLog; -import android.provider.Settings; -import android.telephony.TelephonyManager; -import android.test.mock.MockContentResolver; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.internal.util.test.BroadcastInterceptingContext; -import com.android.internal.util.test.FakeSettingsProvider; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Arrays; -import java.util.Iterator; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class TetheringConfigurationTest { - private final SharedLog mLog = new SharedLog("TetheringConfigurationTest"); - - private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; - @Mock private Context mContext; - @Mock private TelephonyManager mTelephonyManager; - @Mock private Resources mResources; - @Mock private Resources mResourcesForSubId; - private MockContentResolver mContentResolver; - private Context mMockContext; - private boolean mHasTelephonyManager; - - private class MockTetheringConfiguration extends TetheringConfiguration { - MockTetheringConfiguration(Context ctx, SharedLog log, int id) { - super(ctx, log, id); - } - - @Override - protected Resources getResourcesForSubIdWrapper(Context ctx, int subId) { - return mResourcesForSubId; - } - } - - private class MockContext extends BroadcastInterceptingContext { - MockContext(Context base) { - super(base); - } - - @Override - public Resources getResources() { return mResources; } - - @Override - public Object getSystemService(String name) { - if (Context.TELEPHONY_SERVICE.equals(name)) { - return mHasTelephonyManager ? mTelephonyManager : null; - } - return super.getSystemService(name); - } - - @Override - public ContentResolver getContentResolver() { - return mContentResolver; - } - } - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - when(mResources.getStringArray(com.android.internal.R.array.config_tether_dhcp_range)) - .thenReturn(new String[0]); - when(mResources.getStringArray(com.android.internal.R.array.config_tether_usb_regexs)) - .thenReturn(new String[0]); - when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_regexs)) - .thenReturn(new String[]{ "test_wlan\\d" }); - when(mResources.getStringArray(com.android.internal.R.array.config_tether_bluetooth_regexs)) - .thenReturn(new String[0]); - when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[0]); - when(mResources.getStringArray( - com.android.internal.R.array.config_mobile_hotspot_provision_app)) - .thenReturn(new String[0]); - mContentResolver = new MockContentResolver(); - mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); - mHasTelephonyManager = true; - mMockContext = new MockContext(mContext); - } - - private TetheringConfiguration getTetheringConfiguration(int... legacyTetherUpstreamTypes) { - when(mResources.getIntArray(config_tether_upstream_types)).thenReturn( - legacyTetherUpstreamTypes); - return new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - } - - @Test - public void testNoTelephonyManagerMeansNoDun() { - mHasTelephonyManager = false; - final TetheringConfiguration cfg = getTetheringConfiguration( - new int[]{TYPE_MOBILE_DUN, TYPE_WIFI}); - assertFalse(cfg.isDunRequired); - assertFalse(cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN)); - // Just to prove we haven't clobbered Wi-Fi: - assertTrue(cfg.preferredUpstreamIfaceTypes.contains(TYPE_WIFI)); - } - - @Test - public void testDunFromTelephonyManagerMeansDun() { - when(mTelephonyManager.getTetherApnRequired(anyInt())).thenReturn(true); - - final TetheringConfiguration cfgWifi = getTetheringConfiguration(TYPE_WIFI); - final TetheringConfiguration cfgMobileWifiHipri = getTetheringConfiguration( - TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI); - final TetheringConfiguration cfgWifiDun = getTetheringConfiguration( - TYPE_WIFI, TYPE_MOBILE_DUN); - final TetheringConfiguration cfgMobileWifiHipriDun = getTetheringConfiguration( - TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI, TYPE_MOBILE_DUN); - - for (TetheringConfiguration cfg : Arrays.asList(cfgWifi, cfgMobileWifiHipri, - cfgWifiDun, cfgMobileWifiHipriDun)) { - String msg = "config=" + cfg.toString(); - assertTrue(msg, cfg.isDunRequired); - assertTrue(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN)); - assertFalse(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE)); - assertFalse(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_HIPRI)); - // Just to prove we haven't clobbered Wi-Fi: - assertTrue(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_WIFI)); - } - } - - @Test - public void testDunNotRequiredFromTelephonyManagerMeansNoDun() { - when(mTelephonyManager.getTetherApnRequired(anyInt())).thenReturn(false); - - final TetheringConfiguration cfgWifi = getTetheringConfiguration(TYPE_WIFI); - final TetheringConfiguration cfgMobileWifiHipri = getTetheringConfiguration( - TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI); - final TetheringConfiguration cfgWifiDun = getTetheringConfiguration( - TYPE_WIFI, TYPE_MOBILE_DUN); - final TetheringConfiguration cfgWifiMobile = getTetheringConfiguration( - TYPE_WIFI, TYPE_MOBILE); - final TetheringConfiguration cfgWifiHipri = getTetheringConfiguration( - TYPE_WIFI, TYPE_MOBILE_HIPRI); - final TetheringConfiguration cfgMobileWifiHipriDun = getTetheringConfiguration( - TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI, TYPE_MOBILE_DUN); - - String msg; - // TYPE_MOBILE_DUN should be present in none of the combinations. - // TYPE_WIFI should not be affected. - for (TetheringConfiguration cfg : Arrays.asList(cfgWifi, cfgMobileWifiHipri, cfgWifiDun, - cfgWifiMobile, cfgWifiHipri, cfgMobileWifiHipriDun)) { - msg = "config=" + cfg.toString(); - assertFalse(msg, cfg.isDunRequired); - assertFalse(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN)); - assertTrue(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_WIFI)); - } - - for (TetheringConfiguration cfg : Arrays.asList(cfgWifi, cfgMobileWifiHipri, cfgWifiDun, - cfgMobileWifiHipriDun)) { - msg = "config=" + cfg.toString(); - assertTrue(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE)); - assertTrue(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_HIPRI)); - } - msg = "config=" + cfgWifiMobile.toString(); - assertTrue(msg, cfgWifiMobile.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE)); - assertFalse(msg, cfgWifiMobile.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_HIPRI)); - msg = "config=" + cfgWifiHipri.toString(); - assertFalse(msg, cfgWifiHipri.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE)); - assertTrue(msg, cfgWifiHipri.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_HIPRI)); - - } - - @Test - public void testNoDefinedUpstreamTypesAddsEthernet() { - when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[]{}); - when(mTelephonyManager.getTetherApnRequired(anyInt())).thenReturn(false); - - final TetheringConfiguration cfg = new TetheringConfiguration( - mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - final Iterator<Integer> upstreamIterator = cfg.preferredUpstreamIfaceTypes.iterator(); - assertTrue(upstreamIterator.hasNext()); - assertEquals(TYPE_ETHERNET, upstreamIterator.next().intValue()); - // The following is because the code always adds some kind of mobile - // upstream, be it DUN or, in this case where DUN is NOT required, - // make sure there is at least one of MOBILE or HIPRI. With the empty - // list of the configuration in this test, it will always add both - // MOBILE and HIPRI, in that order. - assertTrue(upstreamIterator.hasNext()); - assertEquals(TYPE_MOBILE, upstreamIterator.next().intValue()); - assertTrue(upstreamIterator.hasNext()); - assertEquals(TYPE_MOBILE_HIPRI, upstreamIterator.next().intValue()); - assertFalse(upstreamIterator.hasNext()); - } - - @Test - public void testDefinedUpstreamTypesSansEthernetAddsEthernet() { - when(mResources.getIntArray(config_tether_upstream_types)).thenReturn( - new int[]{TYPE_WIFI, TYPE_MOBILE_HIPRI}); - when(mTelephonyManager.getTetherApnRequired(anyInt())).thenReturn(false); - - final TetheringConfiguration cfg = new TetheringConfiguration( - mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - final Iterator<Integer> upstreamIterator = cfg.preferredUpstreamIfaceTypes.iterator(); - assertTrue(upstreamIterator.hasNext()); - assertEquals(TYPE_ETHERNET, upstreamIterator.next().intValue()); - assertTrue(upstreamIterator.hasNext()); - assertEquals(TYPE_WIFI, upstreamIterator.next().intValue()); - assertTrue(upstreamIterator.hasNext()); - assertEquals(TYPE_MOBILE_HIPRI, upstreamIterator.next().intValue()); - assertFalse(upstreamIterator.hasNext()); - } - - @Test - public void testDefinedUpstreamTypesWithEthernetDoesNotAddEthernet() { - when(mResources.getIntArray(config_tether_upstream_types)) - .thenReturn(new int[]{TYPE_WIFI, TYPE_ETHERNET, TYPE_MOBILE_HIPRI}); - when(mTelephonyManager.getTetherApnRequired(anyInt())).thenReturn(false); - - final TetheringConfiguration cfg = new TetheringConfiguration( - mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - final Iterator<Integer> upstreamIterator = cfg.preferredUpstreamIfaceTypes.iterator(); - assertTrue(upstreamIterator.hasNext()); - assertEquals(TYPE_WIFI, upstreamIterator.next().intValue()); - assertTrue(upstreamIterator.hasNext()); - assertEquals(TYPE_ETHERNET, upstreamIterator.next().intValue()); - assertTrue(upstreamIterator.hasNext()); - assertEquals(TYPE_MOBILE_HIPRI, upstreamIterator.next().intValue()); - assertFalse(upstreamIterator.hasNext()); - } - - @Test - public void testNewDhcpServerDisabled() { - Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1); - - final TetheringConfiguration cfg = new TetheringConfiguration( - mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - assertTrue(cfg.enableLegacyDhcpServer); - } - - @Test - public void testNewDhcpServerEnabled() { - Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0); - - final TetheringConfiguration cfg = new TetheringConfiguration( - mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - assertFalse(cfg.enableLegacyDhcpServer); - } - - @Test - public void testGetResourcesBySubId() { - setUpResourceForSubId(); - final TetheringConfiguration cfg = new TetheringConfiguration( - mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - assertTrue(cfg.provisioningApp.length == 0); - final int anyValidSubId = 1; - final MockTetheringConfiguration mockCfg = - new MockTetheringConfiguration(mMockContext, mLog, anyValidSubId); - assertEquals(mockCfg.provisioningApp[0], PROVISIONING_APP_NAME[0]); - assertEquals(mockCfg.provisioningApp[1], PROVISIONING_APP_NAME[1]); - } - - private void setUpResourceForSubId() { - when(mResourcesForSubId.getStringArray( - com.android.internal.R.array.config_tether_dhcp_range)).thenReturn(new String[0]); - when(mResourcesForSubId.getStringArray( - com.android.internal.R.array.config_tether_usb_regexs)).thenReturn(new String[0]); - when(mResourcesForSubId.getStringArray( - com.android.internal.R.array.config_tether_wifi_regexs)) - .thenReturn(new String[]{ "test_wlan\\d" }); - when(mResourcesForSubId.getStringArray( - com.android.internal.R.array.config_tether_bluetooth_regexs)) - .thenReturn(new String[0]); - when(mResourcesForSubId.getIntArray(config_tether_upstream_types)).thenReturn(new int[0]); - when(mResourcesForSubId.getStringArray( - com.android.internal.R.array.config_mobile_hotspot_provision_app)) - .thenReturn(PROVISIONING_APP_NAME); - } - -} diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java deleted file mode 100644 index 0d276cbd1b85..000000000000 --- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java +++ /dev/null @@ -1,792 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.connectivity.tethering; - -import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; -import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; -import static android.net.ConnectivityManager.TYPE_NONE; -import static android.net.ConnectivityManager.TYPE_WIFI; -import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; -import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; -import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; -import static android.net.NetworkCapabilities.TRANSPORT_WIFI; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.anyString; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.ConnectivityManager.NetworkCallback; -import android.net.IConnectivityManager; -import android.net.IpPrefix; -import android.net.LinkAddress; -import android.net.LinkProperties; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.net.NetworkRequest; -import android.net.NetworkState; -import android.net.util.SharedLog; -import android.os.Handler; -import android.os.Message; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.internal.util.State; -import com.android.internal.util.StateMachine; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class UpstreamNetworkMonitorTest { - private static final int EVENT_UNM_UPDATE = 1; - - private static final boolean INCLUDES = true; - private static final boolean EXCLUDES = false; - - // Actual contents of the request don't matter for this test. The lack of - // any specific TRANSPORT_* is sufficient to identify this request. - private static final NetworkRequest mDefaultRequest = new NetworkRequest.Builder().build(); - - @Mock private Context mContext; - @Mock private EntitlementManager mEntitleMgr; - @Mock private IConnectivityManager mCS; - @Mock private SharedLog mLog; - - private TestStateMachine mSM; - private TestConnectivityManager mCM; - private UpstreamNetworkMonitor mUNM; - - @Before public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - reset(mContext); - reset(mCS); - reset(mLog); - when(mLog.forSubComponent(anyString())).thenReturn(mLog); - when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true); - - mCM = spy(new TestConnectivityManager(mContext, mCS)); - mSM = new TestStateMachine(); - mUNM = new UpstreamNetworkMonitor( - (ConnectivityManager) mCM, mSM, mLog, EVENT_UNM_UPDATE); - } - - @After public void tearDown() throws Exception { - if (mSM != null) { - mSM.quit(); - mSM = null; - } - } - - @Test - public void testStopWithoutStartIsNonFatal() { - mUNM.stop(); - mUNM.stop(); - mUNM.stop(); - } - - @Test - public void testDoesNothingBeforeTrackDefaultAndStarted() throws Exception { - assertTrue(mCM.hasNoCallbacks()); - assertFalse(mUNM.mobileNetworkRequested()); - - mUNM.updateMobileRequiresDun(true); - assertTrue(mCM.hasNoCallbacks()); - mUNM.updateMobileRequiresDun(false); - assertTrue(mCM.hasNoCallbacks()); - } - - @Test - public void testDefaultNetworkIsTracked() throws Exception { - assertTrue(mCM.hasNoCallbacks()); - mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); - - mUNM.startObserveAllNetworks(); - assertEquals(1, mCM.trackingDefault.size()); - - mUNM.stop(); - assertTrue(mCM.onlyHasDefaultCallbacks()); - } - - @Test - public void testListensForAllNetworks() throws Exception { - assertTrue(mCM.listening.isEmpty()); - - mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); - mUNM.startObserveAllNetworks(); - assertFalse(mCM.listening.isEmpty()); - assertTrue(mCM.isListeningForAll()); - - mUNM.stop(); - assertTrue(mCM.onlyHasDefaultCallbacks()); - } - - @Test - public void testCallbacksRegistered() { - mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); - verify(mCM, times(1)).requestNetwork( - eq(mDefaultRequest), any(NetworkCallback.class), any(Handler.class)); - mUNM.startObserveAllNetworks(); - verify(mCM, times(1)).registerNetworkCallback( - any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class)); - - mUNM.stop(); - verify(mCM, times(1)).unregisterNetworkCallback(any(NetworkCallback.class)); - } - - @Test - public void testRequestsMobileNetwork() throws Exception { - assertFalse(mUNM.mobileNetworkRequested()); - assertEquals(0, mCM.requested.size()); - - mUNM.startObserveAllNetworks(); - assertFalse(mUNM.mobileNetworkRequested()); - assertEquals(0, mCM.requested.size()); - - mUNM.updateMobileRequiresDun(false); - assertFalse(mUNM.mobileNetworkRequested()); - assertEquals(0, mCM.requested.size()); - - mUNM.registerMobileNetworkRequest(); - assertTrue(mUNM.mobileNetworkRequested()); - assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI); - assertFalse(mCM.isDunRequested()); - - mUNM.stop(); - assertFalse(mUNM.mobileNetworkRequested()); - assertTrue(mCM.hasNoCallbacks()); - } - - @Test - public void testDuplicateMobileRequestsIgnored() throws Exception { - assertFalse(mUNM.mobileNetworkRequested()); - assertEquals(0, mCM.requested.size()); - - mUNM.startObserveAllNetworks(); - verify(mCM, times(1)).registerNetworkCallback( - any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class)); - assertFalse(mUNM.mobileNetworkRequested()); - assertEquals(0, mCM.requested.size()); - - mUNM.updateMobileRequiresDun(true); - mUNM.registerMobileNetworkRequest(); - verify(mCM, times(1)).requestNetwork( - any(NetworkRequest.class), any(NetworkCallback.class), anyInt(), anyInt(), - any(Handler.class)); - - assertTrue(mUNM.mobileNetworkRequested()); - assertUpstreamTypeRequested(TYPE_MOBILE_DUN); - assertTrue(mCM.isDunRequested()); - - // Try a few things that must not result in any state change. - mUNM.registerMobileNetworkRequest(); - mUNM.updateMobileRequiresDun(true); - mUNM.registerMobileNetworkRequest(); - - assertTrue(mUNM.mobileNetworkRequested()); - assertUpstreamTypeRequested(TYPE_MOBILE_DUN); - assertTrue(mCM.isDunRequested()); - - mUNM.stop(); - verify(mCM, times(2)).unregisterNetworkCallback(any(NetworkCallback.class)); - - verifyNoMoreInteractions(mCM); - } - - @Test - public void testRequestsDunNetwork() throws Exception { - assertFalse(mUNM.mobileNetworkRequested()); - assertEquals(0, mCM.requested.size()); - - mUNM.startObserveAllNetworks(); - assertFalse(mUNM.mobileNetworkRequested()); - assertEquals(0, mCM.requested.size()); - - mUNM.updateMobileRequiresDun(true); - assertFalse(mUNM.mobileNetworkRequested()); - assertEquals(0, mCM.requested.size()); - - mUNM.registerMobileNetworkRequest(); - assertTrue(mUNM.mobileNetworkRequested()); - assertUpstreamTypeRequested(TYPE_MOBILE_DUN); - assertTrue(mCM.isDunRequested()); - - mUNM.stop(); - assertFalse(mUNM.mobileNetworkRequested()); - assertTrue(mCM.hasNoCallbacks()); - } - - @Test - public void testUpdateMobileRequiresDun() throws Exception { - mUNM.startObserveAllNetworks(); - - // Test going from no-DUN to DUN correctly re-registers callbacks. - mUNM.updateMobileRequiresDun(false); - mUNM.registerMobileNetworkRequest(); - assertTrue(mUNM.mobileNetworkRequested()); - assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI); - assertFalse(mCM.isDunRequested()); - mUNM.updateMobileRequiresDun(true); - assertTrue(mUNM.mobileNetworkRequested()); - assertUpstreamTypeRequested(TYPE_MOBILE_DUN); - assertTrue(mCM.isDunRequested()); - - // Test going from DUN to no-DUN correctly re-registers callbacks. - mUNM.updateMobileRequiresDun(false); - assertTrue(mUNM.mobileNetworkRequested()); - assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI); - assertFalse(mCM.isDunRequested()); - - mUNM.stop(); - assertFalse(mUNM.mobileNetworkRequested()); - } - - @Test - public void testSelectPreferredUpstreamType() throws Exception { - final Collection<Integer> preferredTypes = new ArrayList<>(); - preferredTypes.add(TYPE_WIFI); - - mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); - mUNM.startObserveAllNetworks(); - // There are no networks, so there is nothing to select. - assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); - - final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI); - wifiAgent.fakeConnect(); - // WiFi is up, we should prefer it. - assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes)); - wifiAgent.fakeDisconnect(); - // There are no networks, so there is nothing to select. - assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); - - final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); - cellAgent.fakeConnect(); - assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); - - preferredTypes.add(TYPE_MOBILE_DUN); - // This is coupled with preferred types in TetheringConfiguration. - mUNM.updateMobileRequiresDun(true); - // DUN is available, but only use regular cell: no upstream selected. - assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); - preferredTypes.remove(TYPE_MOBILE_DUN); - // No WiFi, but our preferred flavour of cell is up. - preferredTypes.add(TYPE_MOBILE_HIPRI); - // This is coupled with preferred types in TetheringConfiguration. - mUNM.updateMobileRequiresDun(false); - assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI, - mUNM.selectPreferredUpstreamType(preferredTypes)); - // Check to see we filed an explicit request. - assertEquals(1, mCM.requested.size()); - NetworkRequest netReq = (NetworkRequest) mCM.requested.values().toArray()[0]; - assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)); - assertFalse(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)); - // mobile is not permitted, we should not use HIPRI. - when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false); - assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); - assertEquals(0, mCM.requested.size()); - // mobile change back to permitted, HIRPI should come back - when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true); - assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI, - mUNM.selectPreferredUpstreamType(preferredTypes)); - - wifiAgent.fakeConnect(); - // WiFi is up, and we should prefer it over cell. - assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes)); - assertEquals(0, mCM.requested.size()); - - preferredTypes.remove(TYPE_MOBILE_HIPRI); - preferredTypes.add(TYPE_MOBILE_DUN); - // This is coupled with preferred types in TetheringConfiguration. - mUNM.updateMobileRequiresDun(true); - assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes)); - - final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); - dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN); - dunAgent.fakeConnect(); - - // WiFi is still preferred. - assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes)); - - // WiFi goes down, cell and DUN are still up but only DUN is preferred. - wifiAgent.fakeDisconnect(); - assertSatisfiesLegacyType(TYPE_MOBILE_DUN, - mUNM.selectPreferredUpstreamType(preferredTypes)); - // Check to see we filed an explicit request. - assertEquals(1, mCM.requested.size()); - netReq = (NetworkRequest) mCM.requested.values().toArray()[0]; - assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)); - assertTrue(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)); - // mobile is not permitted, we should not use DUN. - when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false); - assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); - assertEquals(0, mCM.requested.size()); - // mobile change back to permitted, DUN should come back - when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true); - assertSatisfiesLegacyType(TYPE_MOBILE_DUN, - mUNM.selectPreferredUpstreamType(preferredTypes)); - } - - @Test - public void testGetCurrentPreferredUpstream() throws Exception { - mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); - mUNM.startObserveAllNetworks(); - mUNM.updateMobileRequiresDun(false); - - // [0] Mobile connects, DUN not required -> mobile selected. - final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); - cellAgent.fakeConnect(); - mCM.makeDefaultNetwork(cellAgent); - assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network); - - // [1] Mobile connects but not permitted -> null selected - when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false); - assertEquals(null, mUNM.getCurrentPreferredUpstream()); - when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true); - - // [2] WiFi connects but not validated/promoted to default -> mobile selected. - final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI); - wifiAgent.fakeConnect(); - assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network); - - // [3] WiFi validates and is promoted to the default network -> WiFi selected. - mCM.makeDefaultNetwork(wifiAgent); - assertEquals(wifiAgent.networkId, mUNM.getCurrentPreferredUpstream().network); - - // [4] DUN required, no other changes -> WiFi still selected - mUNM.updateMobileRequiresDun(true); - assertEquals(wifiAgent.networkId, mUNM.getCurrentPreferredUpstream().network); - - // [5] WiFi no longer validated, mobile becomes default, DUN required -> null selected. - mCM.makeDefaultNetwork(cellAgent); - assertEquals(null, mUNM.getCurrentPreferredUpstream()); - // TODO: make sure that a DUN request has been filed. This is currently - // triggered by code over in Tethering, but once that has been moved - // into UNM we should test for this here. - - // [6] DUN network arrives -> DUN selected - final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); - dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN); - dunAgent.networkCapabilities.removeCapability(NET_CAPABILITY_INTERNET); - dunAgent.fakeConnect(); - assertEquals(dunAgent.networkId, mUNM.getCurrentPreferredUpstream().network); - - // [7] Mobile is not permitted -> null selected - when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false); - assertEquals(null, mUNM.getCurrentPreferredUpstream()); - } - - @Test - public void testLocalPrefixes() throws Exception { - mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); - mUNM.startObserveAllNetworks(); - - // [0] Test minimum set of local prefixes. - Set<IpPrefix> local = mUNM.getLocalPrefixes(); - assertTrue(local.isEmpty()); - - final Set<String> alreadySeen = new HashSet<>(); - - // [1] Pretend Wi-Fi connects. - final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI); - final LinkProperties wifiLp = wifiAgent.linkProperties; - wifiLp.setInterfaceName("wlan0"); - final String[] WIFI_ADDRS = { - "fe80::827a:bfff:fe6f:374d", "100.112.103.18", - "2001:db8:4:fd00:827a:bfff:fe6f:374d", - "2001:db8:4:fd00:6dea:325a:fdae:4ef4", - "fd6a:a640:60bf:e985::123", // ULA address for good measure. - }; - for (String addrStr : WIFI_ADDRS) { - final String cidr = addrStr.contains(":") ? "/64" : "/20"; - wifiLp.addLinkAddress(new LinkAddress(addrStr + cidr)); - } - wifiAgent.fakeConnect(); - wifiAgent.sendLinkProperties(); - - local = mUNM.getLocalPrefixes(); - assertPrefixSet(local, INCLUDES, alreadySeen); - final String[] wifiLinkPrefixes = { - // Link-local prefixes are excluded and dealt with elsewhere. - "100.112.96.0/20", "2001:db8:4:fd00::/64", "fd6a:a640:60bf:e985::/64", - }; - assertPrefixSet(local, INCLUDES, wifiLinkPrefixes); - Collections.addAll(alreadySeen, wifiLinkPrefixes); - assertEquals(alreadySeen.size(), local.size()); - - // [2] Pretend mobile connects. - final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); - final LinkProperties cellLp = cellAgent.linkProperties; - cellLp.setInterfaceName("rmnet_data0"); - final String[] CELL_ADDRS = { - "10.102.211.48", "2001:db8:0:1:b50e:70d9:10c9:433d", - }; - for (String addrStr : CELL_ADDRS) { - final String cidr = addrStr.contains(":") ? "/64" : "/27"; - cellLp.addLinkAddress(new LinkAddress(addrStr + cidr)); - } - cellAgent.fakeConnect(); - cellAgent.sendLinkProperties(); - - local = mUNM.getLocalPrefixes(); - assertPrefixSet(local, INCLUDES, alreadySeen); - final String[] cellLinkPrefixes = { "10.102.211.32/27", "2001:db8:0:1::/64" }; - assertPrefixSet(local, INCLUDES, cellLinkPrefixes); - Collections.addAll(alreadySeen, cellLinkPrefixes); - assertEquals(alreadySeen.size(), local.size()); - - // [3] Pretend DUN connects. - final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); - dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN); - dunAgent.networkCapabilities.removeCapability(NET_CAPABILITY_INTERNET); - final LinkProperties dunLp = dunAgent.linkProperties; - dunLp.setInterfaceName("rmnet_data1"); - final String[] DUN_ADDRS = { - "192.0.2.48", "2001:db8:1:2:b50e:70d9:10c9:433d", - }; - for (String addrStr : DUN_ADDRS) { - final String cidr = addrStr.contains(":") ? "/64" : "/27"; - dunLp.addLinkAddress(new LinkAddress(addrStr + cidr)); - } - dunAgent.fakeConnect(); - dunAgent.sendLinkProperties(); - - local = mUNM.getLocalPrefixes(); - assertPrefixSet(local, INCLUDES, alreadySeen); - final String[] dunLinkPrefixes = { "192.0.2.32/27", "2001:db8:1:2::/64" }; - assertPrefixSet(local, INCLUDES, dunLinkPrefixes); - Collections.addAll(alreadySeen, dunLinkPrefixes); - assertEquals(alreadySeen.size(), local.size()); - - // [4] Pretend Wi-Fi disconnected. It's addresses/prefixes should no - // longer be included (should be properly removed). - wifiAgent.fakeDisconnect(); - local = mUNM.getLocalPrefixes(); - assertPrefixSet(local, EXCLUDES, wifiLinkPrefixes); - assertPrefixSet(local, INCLUDES, cellLinkPrefixes); - assertPrefixSet(local, INCLUDES, dunLinkPrefixes); - - // [5] Pretend mobile disconnected. - cellAgent.fakeDisconnect(); - local = mUNM.getLocalPrefixes(); - assertPrefixSet(local, EXCLUDES, wifiLinkPrefixes); - assertPrefixSet(local, EXCLUDES, cellLinkPrefixes); - assertPrefixSet(local, INCLUDES, dunLinkPrefixes); - - // [6] Pretend DUN disconnected. - dunAgent.fakeDisconnect(); - local = mUNM.getLocalPrefixes(); - assertTrue(local.isEmpty()); - } - - @Test - public void testSelectMobileWhenMobileIsNotDefault() { - final Collection<Integer> preferredTypes = new ArrayList<>(); - // Mobile has higher pirority than wifi. - preferredTypes.add(TYPE_MOBILE_HIPRI); - preferredTypes.add(TYPE_WIFI); - mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); - mUNM.startObserveAllNetworks(); - // Setup wifi and make wifi as default network. - final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI); - wifiAgent.fakeConnect(); - mCM.makeDefaultNetwork(wifiAgent); - // Setup mobile network. - final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); - cellAgent.fakeConnect(); - - assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI, - mUNM.selectPreferredUpstreamType(preferredTypes)); - verify(mEntitleMgr, times(1)).maybeRunProvisioning(); - } - private void assertSatisfiesLegacyType(int legacyType, NetworkState ns) { - if (legacyType == TYPE_NONE) { - assertTrue(ns == null); - return; - } - - final NetworkCapabilities nc = ConnectivityManager.networkCapabilitiesForType(legacyType); - assertTrue(nc.satisfiedByNetworkCapabilities(ns.networkCapabilities)); - } - - private void assertUpstreamTypeRequested(int upstreamType) throws Exception { - assertEquals(1, mCM.requested.size()); - assertEquals(1, mCM.legacyTypeMap.size()); - assertEquals(Integer.valueOf(upstreamType), - mCM.legacyTypeMap.values().iterator().next()); - } - - public static class TestConnectivityManager extends ConnectivityManager { - public Map<NetworkCallback, Handler> allCallbacks = new HashMap<>(); - public Set<NetworkCallback> trackingDefault = new HashSet<>(); - public TestNetworkAgent defaultNetwork = null; - public Map<NetworkCallback, NetworkRequest> listening = new HashMap<>(); - public Map<NetworkCallback, NetworkRequest> requested = new HashMap<>(); - public Map<NetworkCallback, Integer> legacyTypeMap = new HashMap<>(); - - private int mNetworkId = 100; - - public TestConnectivityManager(Context ctx, IConnectivityManager svc) { - super(ctx, svc); - } - - boolean hasNoCallbacks() { - return allCallbacks.isEmpty() - && trackingDefault.isEmpty() - && listening.isEmpty() - && requested.isEmpty() - && legacyTypeMap.isEmpty(); - } - - boolean onlyHasDefaultCallbacks() { - return (allCallbacks.size() == 1) - && (trackingDefault.size() == 1) - && listening.isEmpty() - && requested.isEmpty() - && legacyTypeMap.isEmpty(); - } - - boolean isListeningForAll() { - final NetworkCapabilities empty = new NetworkCapabilities(); - empty.clearAll(); - - for (NetworkRequest req : listening.values()) { - if (req.networkCapabilities.equalRequestableCapabilities(empty)) { - return true; - } - } - return false; - } - - boolean isDunRequested() { - for (NetworkRequest req : requested.values()) { - if (req.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)) { - return true; - } - } - return false; - } - - int getNetworkId() { return ++mNetworkId; } - - void makeDefaultNetwork(TestNetworkAgent agent) { - if (Objects.equals(defaultNetwork, agent)) return; - - final TestNetworkAgent formerDefault = defaultNetwork; - defaultNetwork = agent; - - for (NetworkCallback cb : trackingDefault) { - if (defaultNetwork != null) { - cb.onAvailable(defaultNetwork.networkId); - cb.onCapabilitiesChanged( - defaultNetwork.networkId, defaultNetwork.networkCapabilities); - cb.onLinkPropertiesChanged( - defaultNetwork.networkId, defaultNetwork.linkProperties); - } - } - } - - @Override - public void requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h) { - assertFalse(allCallbacks.containsKey(cb)); - allCallbacks.put(cb, h); - if (mDefaultRequest.equals(req)) { - assertFalse(trackingDefault.contains(cb)); - trackingDefault.add(cb); - } else { - assertFalse(requested.containsKey(cb)); - requested.put(cb, req); - } - } - - @Override - public void requestNetwork(NetworkRequest req, NetworkCallback cb) { - fail("Should never be called."); - } - - @Override - public void requestNetwork(NetworkRequest req, NetworkCallback cb, - int timeoutMs, int legacyType, Handler h) { - assertFalse(allCallbacks.containsKey(cb)); - allCallbacks.put(cb, h); - assertFalse(requested.containsKey(cb)); - requested.put(cb, req); - assertFalse(legacyTypeMap.containsKey(cb)); - if (legacyType != ConnectivityManager.TYPE_NONE) { - legacyTypeMap.put(cb, legacyType); - } - } - - @Override - public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb, Handler h) { - assertFalse(allCallbacks.containsKey(cb)); - allCallbacks.put(cb, h); - assertFalse(listening.containsKey(cb)); - listening.put(cb, req); - } - - @Override - public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb) { - fail("Should never be called."); - } - - @Override - public void registerDefaultNetworkCallback(NetworkCallback cb, Handler h) { - fail("Should never be called."); - } - - @Override - public void registerDefaultNetworkCallback(NetworkCallback cb) { - fail("Should never be called."); - } - - @Override - public void unregisterNetworkCallback(NetworkCallback cb) { - if (trackingDefault.contains(cb)) { - trackingDefault.remove(cb); - } else if (listening.containsKey(cb)) { - listening.remove(cb); - } else if (requested.containsKey(cb)) { - requested.remove(cb); - legacyTypeMap.remove(cb); - } else { - fail("Unexpected callback removed"); - } - allCallbacks.remove(cb); - - assertFalse(allCallbacks.containsKey(cb)); - assertFalse(trackingDefault.contains(cb)); - assertFalse(listening.containsKey(cb)); - assertFalse(requested.containsKey(cb)); - } - } - - public static class TestNetworkAgent { - public final TestConnectivityManager cm; - public final Network networkId; - public final int transportType; - public final NetworkCapabilities networkCapabilities; - public final LinkProperties linkProperties; - - public TestNetworkAgent(TestConnectivityManager cm, int transportType) { - this.cm = cm; - this.networkId = new Network(cm.getNetworkId()); - this.transportType = transportType; - networkCapabilities = new NetworkCapabilities(); - networkCapabilities.addTransportType(transportType); - networkCapabilities.addCapability(NET_CAPABILITY_INTERNET); - linkProperties = new LinkProperties(); - } - - public void fakeConnect() { - for (NetworkCallback cb : cm.listening.keySet()) { - cb.onAvailable(networkId); - cb.onCapabilitiesChanged(networkId, copy(networkCapabilities)); - cb.onLinkPropertiesChanged(networkId, copy(linkProperties)); - } - } - - public void fakeDisconnect() { - for (NetworkCallback cb : cm.listening.keySet()) { - cb.onLost(networkId); - } - } - - public void sendLinkProperties() { - for (NetworkCallback cb : cm.listening.keySet()) { - cb.onLinkPropertiesChanged(networkId, copy(linkProperties)); - } - } - - @Override - public String toString() { - return String.format("TestNetworkAgent: %s %s", networkId, networkCapabilities); - } - } - - public static class TestStateMachine extends StateMachine { - public final ArrayList<Message> messages = new ArrayList<>(); - private final State mLoggingState = new LoggingState(); - - class LoggingState extends State { - @Override public void enter() { messages.clear(); } - - @Override public void exit() { messages.clear(); } - - @Override public boolean processMessage(Message msg) { - messages.add(msg); - return true; - } - } - - public TestStateMachine() { - super("UpstreamNetworkMonitor.TestStateMachine"); - addState(mLoggingState); - setInitialState(mLoggingState); - super.start(); - } - } - - static NetworkCapabilities copy(NetworkCapabilities nc) { - return new NetworkCapabilities(nc); - } - - static LinkProperties copy(LinkProperties lp) { - return new LinkProperties(lp); - } - - static void assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, String... expected) { - final Set<String> expectedSet = new HashSet<>(); - Collections.addAll(expectedSet, expected); - assertPrefixSet(prefixes, expectation, expectedSet); - } - - static void assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, Set<String> expected) { - for (String expectedPrefix : expected) { - final String errStr = expectation ? "did not find" : "found"; - assertEquals( - String.format("Failed expectation: %s prefix: %s", errStr, expectedPrefix), - expectation, prefixes.contains(new IpPrefix(expectedPrefix))); - } - } -} diff --git a/tests/net/java/com/android/server/net/NetworkStatsBaseTest.java b/tests/net/java/com/android/server/net/NetworkStatsBaseTest.java index 28785f7c9726..3aafe0b075f2 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsBaseTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsBaseTest.java @@ -41,6 +41,7 @@ abstract class NetworkStatsBaseTest { static final String TEST_IFACE = "test0"; static final String TEST_IFACE2 = "test1"; static final String TUN_IFACE = "test_nss_tun0"; + static final String TUN_IFACE2 = "test_nss_tun1"; static final int UID_RED = 1001; static final int UID_BLUE = 1002; @@ -107,10 +108,14 @@ abstract class NetworkStatsBaseTest { assertEquals("unexpected operations", operations, entry.operations); } - VpnInfo createVpnInfo(String[] underlyingIfaces) { + static VpnInfo createVpnInfo(String[] underlyingIfaces) { + return createVpnInfo(TUN_IFACE, underlyingIfaces); + } + + static VpnInfo createVpnInfo(String vpnIface, String[] underlyingIfaces) { VpnInfo info = new VpnInfo(); info.ownerUid = UID_VPN; - info.vpnIface = TUN_IFACE; + info.vpnIface = vpnIface; info.underlyingIfaces = underlyingIfaces; return info; } diff --git a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java index 9b4f49c6248f..551498f2c0cc 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java @@ -29,6 +29,7 @@ import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import static com.android.server.net.NetworkStatsCollection.multiplySafe; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; @@ -43,7 +44,6 @@ import android.os.Process; import android.os.UserHandle; import android.telephony.SubscriptionPlan; import android.telephony.TelephonyManager; -import android.test.MoreAsserts; import android.text.format.DateUtils; import android.util.RecurrenceRule; @@ -240,11 +240,11 @@ public class NetworkStatsCollectionTest { 60 * MINUTE_IN_MILLIS, entry); // Verify the set of relevant UIDs for each access level. - MoreAsserts.assertEquals(new int[] { myUid }, + assertArrayEquals(new int[] { myUid }, collection.getRelevantUids(NetworkStatsAccess.Level.DEFAULT)); - MoreAsserts.assertEquals(new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser }, + assertArrayEquals(new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser }, collection.getRelevantUids(NetworkStatsAccess.Level.USER)); - MoreAsserts.assertEquals( + assertArrayEquals( new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser, uidInDifferentUser }, collection.getRelevantUids(NetworkStatsAccess.Level.DEVICE)); @@ -319,33 +319,33 @@ public class NetworkStatsCollectionTest { assertEntry(18322, 75, 15031, 75, history.getValues(i++, null)); assertEntry(527798, 761, 78570, 652, history.getValues(i++, null)); assertEntry(527797, 760, 78570, 651, history.getValues(i++, null)); - assertEntry(10747, 50, 16838, 55, history.getValues(i++, null)); - assertEntry(10747, 49, 16838, 54, history.getValues(i++, null)); + assertEntry(10747, 50, 16839, 55, history.getValues(i++, null)); + assertEntry(10747, 49, 16837, 54, history.getValues(i++, null)); assertEntry(89191, 151, 18021, 140, history.getValues(i++, null)); assertEntry(89190, 150, 18020, 139, history.getValues(i++, null)); - assertEntry(3821, 22, 4525, 26, history.getValues(i++, null)); - assertEntry(3820, 22, 4524, 26, history.getValues(i++, null)); - assertEntry(91686, 159, 18575, 146, history.getValues(i++, null)); - assertEntry(91685, 159, 18575, 146, history.getValues(i++, null)); - assertEntry(8289, 35, 6863, 38, history.getValues(i++, null)); - assertEntry(8289, 35, 6863, 38, history.getValues(i++, null)); + assertEntry(3821, 23, 4525, 26, history.getValues(i++, null)); + assertEntry(3820, 21, 4524, 26, history.getValues(i++, null)); + assertEntry(91686, 159, 18576, 146, history.getValues(i++, null)); + assertEntry(91685, 159, 18574, 146, history.getValues(i++, null)); + assertEntry(8289, 36, 6864, 39, history.getValues(i++, null)); + assertEntry(8289, 34, 6862, 37, history.getValues(i++, null)); assertEntry(113914, 174, 18364, 157, history.getValues(i++, null)); assertEntry(113913, 173, 18364, 157, history.getValues(i++, null)); - assertEntry(11378, 49, 9261, 49, history.getValues(i++, null)); - assertEntry(11377, 48, 9261, 49, history.getValues(i++, null)); - assertEntry(201765, 328, 41808, 291, history.getValues(i++, null)); - assertEntry(201765, 328, 41807, 290, history.getValues(i++, null)); - assertEntry(106106, 218, 39917, 201, history.getValues(i++, null)); - assertEntry(106105, 217, 39917, 201, history.getValues(i++, null)); + assertEntry(11378, 49, 9261, 50, history.getValues(i++, null)); + assertEntry(11377, 48, 9261, 48, history.getValues(i++, null)); + assertEntry(201766, 328, 41808, 291, history.getValues(i++, null)); + assertEntry(201764, 328, 41807, 290, history.getValues(i++, null)); + assertEntry(106106, 219, 39918, 202, history.getValues(i++, null)); + assertEntry(106105, 216, 39916, 200, history.getValues(i++, null)); assertEquals(history.size(), i); // Slice from middle should be untouched history = getHistory(collection, plan, TIME_B - HOUR_IN_MILLIS, TIME_B + HOUR_IN_MILLIS); i = 0; - assertEntry(3821, 22, 4525, 26, history.getValues(i++, null)); - assertEntry(3820, 22, 4524, 26, history.getValues(i++, null)); - assertEntry(91686, 159, 18575, 146, history.getValues(i++, null)); - assertEntry(91685, 159, 18575, 146, history.getValues(i++, null)); + assertEntry(3821, 23, 4525, 26, history.getValues(i++, null)); + assertEntry(3820, 21, 4524, 26, history.getValues(i++, null)); + assertEntry(91686, 159, 18576, 146, history.getValues(i++, null)); + assertEntry(91685, 159, 18574, 146, history.getValues(i++, null)); assertEquals(history.size(), i); } @@ -373,25 +373,25 @@ public class NetworkStatsCollectionTest { assertEntry(527797, 760, 78570, 651, history.getValues(i++, null)); // Cycle point; start data normalization assertEntry(7507, 0, 11763, 0, history.getValues(i++, null)); - assertEntry(7507, 0, 11763, 0, history.getValues(i++, null)); + assertEntry(7507, 0, 11762, 0, history.getValues(i++, null)); assertEntry(62309, 0, 12589, 0, history.getValues(i++, null)); assertEntry(62309, 0, 12588, 0, history.getValues(i++, null)); assertEntry(2669, 0, 3161, 0, history.getValues(i++, null)); assertEntry(2668, 0, 3160, 0, history.getValues(i++, null)); // Anchor point; end data normalization - assertEntry(91686, 159, 18575, 146, history.getValues(i++, null)); - assertEntry(91685, 159, 18575, 146, history.getValues(i++, null)); - assertEntry(8289, 35, 6863, 38, history.getValues(i++, null)); - assertEntry(8289, 35, 6863, 38, history.getValues(i++, null)); + assertEntry(91686, 159, 18576, 146, history.getValues(i++, null)); + assertEntry(91685, 159, 18574, 146, history.getValues(i++, null)); + assertEntry(8289, 36, 6864, 39, history.getValues(i++, null)); + assertEntry(8289, 34, 6862, 37, history.getValues(i++, null)); assertEntry(113914, 174, 18364, 157, history.getValues(i++, null)); assertEntry(113913, 173, 18364, 157, history.getValues(i++, null)); // Cycle point - assertEntry(11378, 49, 9261, 49, history.getValues(i++, null)); - assertEntry(11377, 48, 9261, 49, history.getValues(i++, null)); - assertEntry(201765, 328, 41808, 291, history.getValues(i++, null)); - assertEntry(201765, 328, 41807, 290, history.getValues(i++, null)); - assertEntry(106106, 218, 39917, 201, history.getValues(i++, null)); - assertEntry(106105, 217, 39917, 201, history.getValues(i++, null)); + assertEntry(11378, 49, 9261, 50, history.getValues(i++, null)); + assertEntry(11377, 48, 9261, 48, history.getValues(i++, null)); + assertEntry(201766, 328, 41808, 291, history.getValues(i++, null)); + assertEntry(201764, 328, 41807, 290, history.getValues(i++, null)); + assertEntry(106106, 219, 39918, 202, history.getValues(i++, null)); + assertEntry(106105, 216, 39916, 200, history.getValues(i++, null)); assertEquals(history.size(), i); // Slice from middle should be augmented @@ -399,8 +399,8 @@ public class NetworkStatsCollectionTest { TIME_B + HOUR_IN_MILLIS); i = 0; assertEntry(2669, 0, 3161, 0, history.getValues(i++, null)); assertEntry(2668, 0, 3160, 0, history.getValues(i++, null)); - assertEntry(91686, 159, 18575, 146, history.getValues(i++, null)); - assertEntry(91685, 159, 18575, 146, history.getValues(i++, null)); + assertEntry(91686, 159, 18576, 146, history.getValues(i++, null)); + assertEntry(91685, 159, 18574, 146, history.getValues(i++, null)); assertEquals(history.size(), i); } @@ -427,34 +427,34 @@ public class NetworkStatsCollectionTest { assertEntry(527798, 761, 78570, 652, history.getValues(i++, null)); assertEntry(527797, 760, 78570, 651, history.getValues(i++, null)); // Cycle point; start data normalization - assertEntry(15015, 0, 23526, 0, history.getValues(i++, null)); - assertEntry(15015, 0, 23526, 0, history.getValues(i++, null)); + assertEntry(15015, 0, 23527, 0, history.getValues(i++, null)); + assertEntry(15015, 0, 23524, 0, history.getValues(i++, null)); assertEntry(124619, 0, 25179, 0, history.getValues(i++, null)); assertEntry(124618, 0, 25177, 0, history.getValues(i++, null)); assertEntry(5338, 0, 6322, 0, history.getValues(i++, null)); assertEntry(5337, 0, 6320, 0, history.getValues(i++, null)); // Anchor point; end data normalization - assertEntry(91686, 159, 18575, 146, history.getValues(i++, null)); - assertEntry(91685, 159, 18575, 146, history.getValues(i++, null)); - assertEntry(8289, 35, 6863, 38, history.getValues(i++, null)); - assertEntry(8289, 35, 6863, 38, history.getValues(i++, null)); + assertEntry(91686, 159, 18576, 146, history.getValues(i++, null)); + assertEntry(91685, 159, 18574, 146, history.getValues(i++, null)); + assertEntry(8289, 36, 6864, 39, history.getValues(i++, null)); + assertEntry(8289, 34, 6862, 37, history.getValues(i++, null)); assertEntry(113914, 174, 18364, 157, history.getValues(i++, null)); assertEntry(113913, 173, 18364, 157, history.getValues(i++, null)); // Cycle point - assertEntry(11378, 49, 9261, 49, history.getValues(i++, null)); - assertEntry(11377, 48, 9261, 49, history.getValues(i++, null)); - assertEntry(201765, 328, 41808, 291, history.getValues(i++, null)); - assertEntry(201765, 328, 41807, 290, history.getValues(i++, null)); - assertEntry(106106, 218, 39917, 201, history.getValues(i++, null)); - assertEntry(106105, 217, 39917, 201, history.getValues(i++, null)); + assertEntry(11378, 49, 9261, 50, history.getValues(i++, null)); + assertEntry(11377, 48, 9261, 48, history.getValues(i++, null)); + assertEntry(201766, 328, 41808, 291, history.getValues(i++, null)); + assertEntry(201764, 328, 41807, 290, history.getValues(i++, null)); + assertEntry(106106, 219, 39918, 202, history.getValues(i++, null)); + assertEntry(106105, 216, 39916, 200, history.getValues(i++, null)); // Slice from middle should be augmented history = getHistory(collection, plan, TIME_B - HOUR_IN_MILLIS, TIME_B + HOUR_IN_MILLIS); i = 0; assertEntry(5338, 0, 6322, 0, history.getValues(i++, null)); assertEntry(5337, 0, 6320, 0, history.getValues(i++, null)); - assertEntry(91686, 159, 18575, 146, history.getValues(i++, null)); - assertEntry(91685, 159, 18575, 146, history.getValues(i++, null)); + assertEntry(91686, 159, 18576, 146, history.getValues(i++, null)); + assertEntry(91685, 159, 18574, 146, history.getValues(i++, null)); assertEquals(history.size(), i); } } diff --git a/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java index 7329474db70f..e4996d981fac 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java @@ -79,8 +79,7 @@ public class NetworkStatsFactoryTest extends NetworkStatsBaseTest { // related to networkStatsFactory is compiled to a minimal native library and loaded here. System.loadLibrary("networkstatsfactorytestjni"); mFactory = new NetworkStatsFactory(mTestProc, false); - NetworkStatsFactory.updateVpnInfos(new VpnInfo[0]); - NetworkStatsFactory.clearStackedIfaces(); + mFactory.updateVpnInfos(new VpnInfo[0]); } @After @@ -105,9 +104,9 @@ public class NetworkStatsFactoryTest extends NetworkStatsBaseTest { } @Test - public void vpnRewriteTrafficThroughItself() throws Exception { + public void testVpnRewriteTrafficThroughItself() throws Exception { VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; - NetworkStatsFactory.updateVpnInfos(vpnInfos); + mFactory.updateVpnInfos(vpnInfos); // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption // overhead per packet): @@ -134,10 +133,10 @@ public class NetworkStatsFactoryTest extends NetworkStatsBaseTest { } @Test - public void vpnWithClat() throws Exception { + public void testVpnWithClat() throws Exception { VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {CLAT_PREFIX + TEST_IFACE})}; - NetworkStatsFactory.updateVpnInfos(vpnInfos); - NetworkStatsFactory.noteStackedIface(CLAT_PREFIX + TEST_IFACE, TEST_IFACE); + mFactory.updateVpnInfos(vpnInfos); + mFactory.noteStackedIface(CLAT_PREFIX + TEST_IFACE, TEST_IFACE); // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption // overhead per packet): @@ -167,9 +166,9 @@ public class NetworkStatsFactoryTest extends NetworkStatsBaseTest { } @Test - public void vpnWithOneUnderlyingIface() throws Exception { + public void testVpnWithOneUnderlyingIface() throws Exception { VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; - NetworkStatsFactory.updateVpnInfos(vpnInfos); + mFactory.updateVpnInfos(vpnInfos); // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption // overhead per packet): @@ -190,10 +189,10 @@ public class NetworkStatsFactoryTest extends NetworkStatsBaseTest { } @Test - public void vpnWithOneUnderlyingIfaceAndOwnTraffic() throws Exception { + public void testVpnWithOneUnderlyingIfaceAndOwnTraffic() throws Exception { // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE). VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; - NetworkStatsFactory.updateVpnInfos(vpnInfos); + mFactory.updateVpnInfos(vpnInfos); // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption // overhead per packet): @@ -218,10 +217,10 @@ public class NetworkStatsFactoryTest extends NetworkStatsBaseTest { } @Test - public void vpnWithOneUnderlyingIface_withCompression() throws Exception { + public void testVpnWithOneUnderlyingIface_withCompression() throws Exception { // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE). VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; - NetworkStatsFactory.updateVpnInfos(vpnInfos); + mFactory.updateVpnInfos(vpnInfos); // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption // overhead per packet): @@ -239,12 +238,12 @@ public class NetworkStatsFactoryTest extends NetworkStatsBaseTest { } @Test - public void vpnWithTwoUnderlyingIfaces_packetDuplication() throws Exception { + public void testVpnWithTwoUnderlyingIfaces_packetDuplication() throws Exception { // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set. // Additionally, VPN is duplicating traffic across both WiFi and Cell. VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})}; - NetworkStatsFactory.updateVpnInfos(vpnInfos); + mFactory.updateVpnInfos(vpnInfos); // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption // overhead per packet): @@ -265,12 +264,52 @@ public class NetworkStatsFactoryTest extends NetworkStatsBaseTest { } @Test - public void vpnWithTwoUnderlyingIfaces_splitTraffic() throws Exception { + public void testConcurrentVpns() throws Exception { + // Assume two VPNs are connected on two different network interfaces. VPN1 is using + // TEST_IFACE and VPN2 is using TEST_IFACE2. + final VpnInfo[] vpnInfos = new VpnInfo[] { + createVpnInfo(TUN_IFACE, new String[] {TEST_IFACE}), + createVpnInfo(TUN_IFACE2, new String[] {TEST_IFACE2})}; + mFactory.updateVpnInfos(vpnInfos); + + // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption + // overhead per packet): + // 1000 bytes (100 packets) were sent, and 2000 bytes (200 packets) were received by UID_RED + // over VPN1. + // 700 bytes (70 packets) were sent, and 3000 bytes (300 packets) were received by UID_RED + // over VPN2. + // 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE + // over VPN1. + // 250 bytes (25 packets) were sent, and 500 bytes (50 packets) were received by UID_BLUE + // over VPN2. + // VPN1 sent 1650 bytes (150 packets), and received 3300 (300 packets) over TEST_IFACE. + // Of 1650 bytes sent over WiFi, expect 1000 bytes attributed to UID_RED, 500 bytes + // attributed to UID_BLUE, and 150 bytes attributed to UID_VPN. + // Of 3300 bytes received over WiFi, expect 2000 bytes attributed to UID_RED, 1000 bytes + // attributed to UID_BLUE, and 300 bytes attributed to UID_VPN. + // VPN2 sent 1045 bytes (95 packets), and received 3850 (350 packets) over TEST_IFACE2. + // Of 1045 bytes sent over Cell, expect 700 bytes attributed to UID_RED, 250 bytes + // attributed to UID_BLUE, and 95 bytes attributed to UID_VPN. + // Of 3850 bytes received over Cell, expect 3000 bytes attributed to UID_RED, 500 bytes + // attributed to UID_BLUE, and 350 bytes attributed to UID_VPN. + final NetworkStats tunStats = + parseDetailedStats(R.raw.xt_qtaguid_vpn_one_underlying_two_vpn); + + assertValues(tunStats, TEST_IFACE, UID_RED, 2000L, 200L, 1000L, 100L); + assertValues(tunStats, TEST_IFACE, UID_BLUE, 1000L, 100L, 500L, 50L); + assertValues(tunStats, TEST_IFACE2, UID_RED, 3000L, 300L, 700L, 70L); + assertValues(tunStats, TEST_IFACE2, UID_BLUE, 500L, 50L, 250L, 25L); + assertValues(tunStats, TEST_IFACE, UID_VPN, 300L, 0L, 150L, 0L); + assertValues(tunStats, TEST_IFACE2, UID_VPN, 350L, 0L, 95L, 0L); + } + + @Test + public void testVpnWithTwoUnderlyingIfaces_splitTraffic() throws Exception { // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set. // Additionally, VPN is arbitrarily splitting traffic across WiFi and Cell. VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})}; - NetworkStatsFactory.updateVpnInfos(vpnInfos); + mFactory.updateVpnInfos(vpnInfos); // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption // overhead per packet): @@ -292,12 +331,12 @@ public class NetworkStatsFactoryTest extends NetworkStatsBaseTest { } @Test - public void vpnWithTwoUnderlyingIfaces_splitTrafficWithCompression() throws Exception { + public void testVpnWithTwoUnderlyingIfaces_splitTrafficWithCompression() throws Exception { // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set. // Additionally, VPN is arbitrarily splitting compressed traffic across WiFi and Cell. VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})}; - NetworkStatsFactory.updateVpnInfos(vpnInfos); + mFactory.updateVpnInfos(vpnInfos); // create some traffic (assume 10 bytes of MTU for VPN interface: // 1000 bytes (100 packets) were sent/received by UID_RED over VPN. @@ -315,11 +354,11 @@ public class NetworkStatsFactoryTest extends NetworkStatsBaseTest { } @Test - public void vpnWithIncorrectUnderlyingIface() throws Exception { + public void testVpnWithIncorrectUnderlyingIface() throws Exception { // WiFi and Cell networks are connected and VPN is using Cell (which has TEST_IFACE2), // but has declared only WiFi (TEST_IFACE) in its underlying network set. VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; - NetworkStatsFactory.updateVpnInfos(vpnInfos); + mFactory.updateVpnInfos(vpnInfos); // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption // overhead per packet): @@ -383,7 +422,7 @@ public class NetworkStatsFactoryTest extends NetworkStatsBaseTest { @Test public void testDoubleClatAccountingSimple() throws Exception { - NetworkStatsFactory.noteStackedIface("v4-wlan0", "wlan0"); + mFactory.noteStackedIface("v4-wlan0", "wlan0"); // xt_qtaguid_with_clat_simple is a synthetic file that simulates // - 213 received 464xlat packets of size 200 bytes @@ -398,7 +437,7 @@ public class NetworkStatsFactoryTest extends NetworkStatsBaseTest { @Test public void testDoubleClatAccounting() throws Exception { - NetworkStatsFactory.noteStackedIface("v4-wlan0", "wlan0"); + mFactory.noteStackedIface("v4-wlan0", "wlan0"); NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat); assertEquals(42, stats.size()); @@ -407,7 +446,7 @@ public class NetworkStatsFactoryTest extends NetworkStatsBaseTest { assertStatsEntry(stats, "v4-wlan0", 1000, SET_DEFAULT, 0x0, 30812L, 2310L); assertStatsEntry(stats, "v4-wlan0", 10102, SET_DEFAULT, 0x0, 10022L, 3330L); assertStatsEntry(stats, "v4-wlan0", 10060, SET_DEFAULT, 0x0, 9532772L, 254112L); - assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 15229L, 0L); + assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 0L, 0L); assertStatsEntry(stats, "wlan0", 1000, SET_DEFAULT, 0x0, 6126L, 2013L); assertStatsEntry(stats, "wlan0", 10013, SET_DEFAULT, 0x0, 0L, 144L); assertStatsEntry(stats, "wlan0", 10018, SET_DEFAULT, 0x0, 5980263L, 167667L); @@ -419,8 +458,6 @@ public class NetworkStatsFactoryTest extends NetworkStatsBaseTest { assertStatsEntry(stats, "lo", 0, SET_DEFAULT, 0x0, 1288L, 1288L); assertNoStatsEntry(stats, "wlan0", 1029, SET_DEFAULT, 0x0); - - NetworkStatsFactory.clearStackedIfaces(); } @Test @@ -431,24 +468,20 @@ public class NetworkStatsFactoryTest extends NetworkStatsBaseTest { long appRxBytesAfter = 439237478L; assertEquals("App traffic should be ~100MB", 110553449, appRxBytesAfter - appRxBytesBefore); - long rootRxBytesBefore = 1394011L; - long rootRxBytesAfter = 1398634L; - assertEquals("UID 0 traffic should be ~0", 4623, rootRxBytesAfter - rootRxBytesBefore); + long rootRxBytes = 330187296L; - NetworkStatsFactory.noteStackedIface("v4-wlan0", "wlan0"); + mFactory.noteStackedIface("v4-wlan0", "wlan0"); NetworkStats stats; // Stats snapshot before the download stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat_100mb_download_before); assertStatsEntry(stats, "v4-wlan0", 10106, SET_FOREGROUND, 0x0, appRxBytesBefore, 5199872L); - assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, rootRxBytesBefore, 0L); + assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, rootRxBytes, 0L); // Stats snapshot after the download stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat_100mb_download_after); assertStatsEntry(stats, "v4-wlan0", 10106, SET_FOREGROUND, 0x0, appRxBytesAfter, 7867488L); - assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, rootRxBytesAfter, 0L); - - NetworkStatsFactory.clearStackedIfaces(); + assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, rootRxBytes, 0L); } /** diff --git a/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java index f21a7dd25946..a6f7a36ff01b 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java @@ -28,8 +28,6 @@ import static android.net.NetworkTemplate.buildTemplateWifiWildcard; import static android.net.TrafficStats.MB_IN_BYTES; import static android.text.format.DateUtils.MINUTE_IN_MILLIS; -import static com.android.internal.util.TestUtils.waitForIdleHandler; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; @@ -55,6 +53,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.net.NetworkStatsServiceTest.LatchedHandler; +import com.android.testutils.HandlerUtilsKt; import org.junit.Before; import org.junit.Test; @@ -241,7 +240,7 @@ public class NetworkStatsObserversTest { // Baseline NetworkStats xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */) - .addIfaceValues(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L); + .insertEntry(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L); NetworkStats uidSnapshot = null; mStatsObservers.updateStats( @@ -265,14 +264,14 @@ public class NetworkStatsObserversTest { // Baseline NetworkStats xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */) - .addIfaceValues(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L); + .insertEntry(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L); NetworkStats uidSnapshot = null; mStatsObservers.updateStats( xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); // Delta xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */) - .addIfaceValues(TEST_IFACE, BASE_BYTES + 1024L, 10L, BASE_BYTES + 2048L, 20L); + .insertEntry(TEST_IFACE, BASE_BYTES + 1024L, 10L, BASE_BYTES + 2048L, 20L); mStatsObservers.updateStats( xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); waitForObserverToIdle(); @@ -295,14 +294,14 @@ public class NetworkStatsObserversTest { // Baseline NetworkStats xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */) - .addIfaceValues(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L); + .insertEntry(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L); NetworkStats uidSnapshot = null; mStatsObservers.updateStats( xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); // Delta xtSnapshot = new NetworkStats(TEST_START + MINUTE_IN_MILLIS, 1 /* initialSize */) - .addIfaceValues(TEST_IFACE, BASE_BYTES + THRESHOLD_BYTES, 12L, + .insertEntry(TEST_IFACE, BASE_BYTES + THRESHOLD_BYTES, 12L, BASE_BYTES + THRESHOLD_BYTES, 22L); mStatsObservers.updateStats( xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); @@ -327,14 +326,14 @@ public class NetworkStatsObserversTest { // Baseline NetworkStats xtSnapshot = null; NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L); mStatsObservers.updateStats( xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); // Delta uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L); mStatsObservers.updateStats( @@ -360,14 +359,14 @@ public class NetworkStatsObserversTest { // Baseline NetworkStats xtSnapshot = null; NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L); mStatsObservers.updateStats( xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); // Delta uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L); mStatsObservers.updateStats( @@ -392,14 +391,14 @@ public class NetworkStatsObserversTest { // Baseline NetworkStats xtSnapshot = null; NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L); mStatsObservers.updateStats( xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); // Delta uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L); mStatsObservers.updateStats( @@ -425,14 +424,14 @@ public class NetworkStatsObserversTest { // Baseline NetworkStats xtSnapshot = null; NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */) - .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO, + .insertEntry(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L); mStatsObservers.updateStats( xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); // Delta uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */) - .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO, + .insertEntry(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L); mStatsObservers.updateStats( @@ -441,7 +440,7 @@ public class NetworkStatsObserversTest { } private void waitForObserverToIdle() { - waitForIdleHandler(mObserverHandlerThread, WAIT_TIMEOUT_MS); - waitForIdleHandler(mHandler, WAIT_TIMEOUT_MS); + HandlerUtilsKt.waitForIdle(mObserverHandlerThread, WAIT_TIMEOUT_MS); + HandlerUtilsKt.waitForIdle(mHandler, WAIT_TIMEOUT_MS); } } diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java index 956b2a74329d..a1bb0d586916 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java @@ -37,10 +37,12 @@ import static android.net.NetworkStats.SET_DEFAULT; import static android.net.NetworkStats.SET_FOREGROUND; import static android.net.NetworkStats.STATS_PER_IFACE; import static android.net.NetworkStats.STATS_PER_UID; +import static android.net.NetworkStats.TAG_ALL; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkStatsHistory.FIELD_ALL; import static android.net.NetworkTemplate.buildTemplateMobileAll; +import static android.net.NetworkTemplate.buildTemplateMobileWithRatType; import static android.net.NetworkTemplate.buildTemplateWifiWildcard; import static android.net.TrafficStats.MB_IN_BYTES; import static android.net.TrafficStats.UID_REMOVED; @@ -50,7 +52,6 @@ import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import static android.text.format.DateUtils.WEEK_IN_MILLIS; -import static com.android.internal.util.TestUtils.waitForIdleHandler; import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL; import static org.junit.Assert.assertEquals; @@ -59,11 +60,13 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.NonNull; import android.app.AlarmManager; import android.app.usage.NetworkStatsManager; import android.content.Context; @@ -80,6 +83,7 @@ import android.net.NetworkState; import android.net.NetworkStats; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; +import android.net.netstats.provider.INetworkStatsProviderCallback; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; @@ -96,10 +100,13 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.net.VpnInfo; import com.android.internal.util.ArrayUtils; import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.server.net.NetworkStatsService.NetworkStatsSettings; import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config; +import com.android.testutils.HandlerUtilsKt; +import com.android.testutils.TestableNetworkStatsProviderBinder; import libcore.io.IoUtils; @@ -116,6 +123,7 @@ import java.io.File; import java.time.Clock; import java.time.ZoneOffset; import java.util.Objects; +import java.util.concurrent.Executor; /** * Tests for {@link NetworkStatsService}. @@ -154,11 +162,13 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { private File mStatsDir; private @Mock INetworkManagementService mNetManager; + private @Mock NetworkStatsFactory mStatsFactory; private @Mock NetworkStatsSettings mSettings; private @Mock IBinder mBinder; private @Mock AlarmManager mAlarmManager; + @Mock + private NetworkStatsSubscriptionsMonitor mNetworkStatsSubscriptionsMonitor; private HandlerThread mHandlerThread; - private Handler mHandler; private NetworkStatsService mService; private INetworkStatsSession mSession; @@ -187,15 +197,11 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); - mService = new NetworkStatsService( - mServiceContext, mNetManager, mAlarmManager, wakeLock, mClock, - TelephonyManager.getDefault(), mSettings, new NetworkStatsObservers(), - mStatsDir, getBaseDir(mStatsDir)); mHandlerThread = new HandlerThread("HandlerThread"); - mHandlerThread.start(); - Handler.Callback callback = new NetworkStatsService.HandlerCallback(mService); - mHandler = new Handler(mHandlerThread.getLooper(), callback); - mService.setHandler(mHandler, callback); + final NetworkStatsService.Dependencies deps = makeDependencies(); + mService = new NetworkStatsService(mServiceContext, mNetManager, mAlarmManager, wakeLock, + mClock, mSettings, mStatsFactory, new NetworkStatsObservers(), mStatsDir, + getBaseDir(mStatsDir), deps); mElapsedRealtime = 0L; @@ -205,18 +211,36 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { mService.systemReady(); // Verify that system ready fetches realtime stats - verify(mNetManager).getNetworkStatsUidDetail(UID_ALL, INTERFACES_ALL); + verify(mStatsFactory).readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL); mSession = mService.openSession(); assertNotNull("openSession() failed", mSession); // catch INetworkManagementEventObserver during systemReady() ArgumentCaptor<INetworkManagementEventObserver> networkObserver = - ArgumentCaptor.forClass(INetworkManagementEventObserver.class); + ArgumentCaptor.forClass(INetworkManagementEventObserver.class); verify(mNetManager).registerObserver(networkObserver.capture()); mNetworkObserver = networkObserver.getValue(); } + @NonNull + private NetworkStatsService.Dependencies makeDependencies() { + return new NetworkStatsService.Dependencies() { + @Override + public HandlerThread makeHandlerThread() { + return mHandlerThread; + } + + @Override + public NetworkStatsSubscriptionsMonitor makeSubscriptionsMonitor( + @NonNull Context context, @NonNull Executor executor, + @NonNull NetworkStatsService service) { + + return mNetworkStatsSubscriptionsMonitor; + } + }; + } + @After public void tearDown() throws Exception { IoUtils.deleteContents(mStatsDir); @@ -229,6 +253,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { mSession.close(); mService = null; + + mHandlerThread.quitSafely(); } @Test @@ -239,9 +265,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { NetworkState[] states = new NetworkState[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); - expectBandwidthControlCheck(); - mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states)); + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]); // verify service has empty history for wifi assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0); @@ -251,7 +276,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { incrementCurrentTime(HOUR_IN_MILLIS); expectDefaultSettings(); expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) - .addIfaceValues(TEST_IFACE, 1024L, 1L, 2048L, 2L)); + .insertEntry(TEST_IFACE, 1024L, 1L, 2048L, 2L)); expectNetworkStatsUidDetail(buildEmptyStats()); forcePollAndWaitForIdle(); @@ -264,7 +289,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { incrementCurrentTime(DAY_IN_MILLIS); expectDefaultSettings(); expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) - .addIfaceValues(TEST_IFACE, 4096L, 4L, 8192L, 8L)); + .insertEntry(TEST_IFACE, 4096L, 4L, 8192L, 8L)); expectNetworkStatsUidDetail(buildEmptyStats()); forcePollAndWaitForIdle(); @@ -283,9 +308,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { NetworkState[] states = new NetworkState[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); - expectBandwidthControlCheck(); - mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states)); + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]); // verify service has empty history for wifi assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0); @@ -295,13 +319,13 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { incrementCurrentTime(HOUR_IN_MILLIS); expectDefaultSettings(); expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) - .addIfaceValues(TEST_IFACE, 1024L, 8L, 2048L, 16L)); + .insertEntry(TEST_IFACE, 1024L, 8L, 2048L, 16L)); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 256L, 2L, 128L, 1L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 512L, 4L, 256L, 2L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 256L, 2L, 128L, 1L, 0L) - .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L)); + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 256L, 2L, 128L, 1L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 512L, 4L, 256L, 2L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 256L, 2L, 128L, 1L, 0L) + .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L)); mService.setUidForeground(UID_RED, false); mService.incrementOperationCount(UID_RED, 0xFAAD, 4); mService.setUidForeground(UID_RED, true); @@ -357,15 +381,14 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { NetworkState[] states = new NetworkState[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); - expectBandwidthControlCheck(); - mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states)); + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]); // modify some number on wifi, and trigger poll event incrementCurrentTime(2 * HOUR_IN_MILLIS); expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS); expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) - .addIfaceValues(TEST_IFACE, 512L, 4L, 512L, 4L)); + .insertEntry(TEST_IFACE, 512L, 4L, 512L, 4L)); expectNetworkStatsUidDetail(buildEmptyStats()); forcePollAndWaitForIdle(); @@ -398,19 +421,18 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { NetworkState[] states = new NetworkState[] {buildMobile3gState(IMSI_1)}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); - expectBandwidthControlCheck(); - mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states)); + mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]); // create some traffic on first network incrementCurrentTime(HOUR_IN_MILLIS); expectDefaultSettings(); expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) - .addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L)); + .insertEntry(TEST_IFACE, 2048L, 16L, 512L, 4L)); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L) - .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L)); + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L) + .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L)); mService.incrementOperationCount(UID_RED, 0xF00D, 10); forcePollAndWaitForIdle(); @@ -428,14 +450,13 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { expectDefaultSettings(); states = new NetworkState[] {buildMobile3gState(IMSI_2)}; expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) - .addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L)); + .insertEntry(TEST_IFACE, 2048L, 16L, 512L, 4L)); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L) - .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L)); - expectBandwidthControlCheck(); + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L) + .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L)); - mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states)); + mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]); forcePollAndWaitForIdle(); @@ -443,12 +464,12 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { incrementCurrentTime(HOUR_IN_MILLIS); expectDefaultSettings(); expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) - .addIfaceValues(TEST_IFACE, 2176L, 17L, 1536L, 12L)); + .insertEntry(TEST_IFACE, 2176L, 17L, 1536L, 12L)); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L) - .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 640L, 5L, 1024L, 8L, 0L) - .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xFAAD, 128L, 1L, 1024L, 8L, 0L)); + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L) + .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 640L, 5L, 1024L, 8L, 0L) + .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xFAAD, 128L, 1L, 1024L, 8L, 0L)); mService.incrementOperationCount(UID_BLUE, 0xFAAD, 10); forcePollAndWaitForIdle(); @@ -473,20 +494,20 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { NetworkState[] states = new NetworkState[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); - expectBandwidthControlCheck(); - mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states)); + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]); // create some traffic incrementCurrentTime(HOUR_IN_MILLIS); expectDefaultSettings(); expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) - .addIfaceValues(TEST_IFACE, 4128L, 258L, 544L, 34L)); + .insertEntry(TEST_IFACE, 4128L, 258L, 544L, 34L)); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L) - .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L) - .addValues(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)); + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L) + .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, + 4096L, 258L, 512L, 32L, 0L) + .insertEntry(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)); mService.incrementOperationCount(UID_RED, 0xFAAD, 10); forcePollAndWaitForIdle(); @@ -502,12 +523,13 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { // special "removed" bucket. expectDefaultSettings(); expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) - .addIfaceValues(TEST_IFACE, 4128L, 258L, 544L, 34L)); + .insertEntry(TEST_IFACE, 4128L, 258L, 544L, 34L)); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L) - .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L) - .addValues(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)); + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L) + .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, + 4096L, 258L, 512L, 32L, 0L) + .insertEntry(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)); final Intent intent = new Intent(ACTION_UID_REMOVED); intent.putExtra(EXTRA_UID, UID_BLUE); mServiceContext.sendBroadcast(intent); @@ -525,23 +547,22 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { } @Test - public void testUid3g4gCombinedByTemplate() throws Exception { + public void testUid3gWimaxCombinedByTemplate() throws Exception { // pretend that network comes online expectDefaultSettings(); NetworkState[] states = new NetworkState[] {buildMobile3gState(IMSI_1)}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); - expectBandwidthControlCheck(); - mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states)); + mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]); // create some traffic incrementCurrentTime(HOUR_IN_MILLIS); expectDefaultSettings(); expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)); + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)); mService.incrementOperationCount(UID_RED, 0xF00D, 5); forcePollAndWaitForIdle(); @@ -550,17 +571,16 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { assertUidTotal(sTemplateImsi1, UID_RED, 1024L, 8L, 1024L, 8L, 5); - // now switch over to 4g network + // now switch over to wimax network incrementCurrentTime(HOUR_IN_MILLIS); expectDefaultSettings(); - states = new NetworkState[] {buildMobile4gState(TEST_IFACE2)}; + states = new NetworkState[] {buildWimaxState(TEST_IFACE2)}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)); - expectBandwidthControlCheck(); + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)); - mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states)); + mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]); forcePollAndWaitForIdle(); @@ -569,10 +589,10 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { expectDefaultSettings(); expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L) - .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L) - .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, 0xFAAD, 512L, 4L, 256L, 2L, 0L)); + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L) + .insertEntry(TEST_IFACE2, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L) + .insertEntry(TEST_IFACE2, UID_RED, SET_DEFAULT, 0xFAAD, 512L, 4L, 256L, 2L, 0L)); mService.incrementOperationCount(UID_RED, 0xFAAD, 5); forcePollAndWaitForIdle(); @@ -582,24 +602,105 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { } @Test + public void testMobileStatsByRatType() throws Exception { + final NetworkTemplate template3g = + buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_UMTS); + final NetworkTemplate template4g = + buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_LTE); + final NetworkTemplate template5g = + buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_NR); + final NetworkState[] states = new NetworkState[]{buildMobile3gState(IMSI_1)}; + + // 3G network comes online. + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + + setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_UMTS); + mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), + new VpnInfo[0]); + + // Create some traffic. + incrementCurrentTime(MINUTE_IN_MILLIS); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, + 12L, 18L, 14L, 1L, 0L))); + forcePollAndWaitForIdle(); + + // Verify 3g templates gets stats. + assertUidTotal(sTemplateImsi1, UID_RED, 12L, 18L, 14L, 1L, 0); + assertUidTotal(template3g, UID_RED, 12L, 18L, 14L, 1L, 0); + assertUidTotal(template4g, UID_RED, 0L, 0L, 0L, 0L, 0); + assertUidTotal(template5g, UID_RED, 0L, 0L, 0L, 0L, 0); + + // 4G network comes online. + incrementCurrentTime(MINUTE_IN_MILLIS); + setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_LTE); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) + // Append more traffic on existing 3g stats entry. + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, + 16L, 22L, 17L, 2L, 0L)) + // Add entry that is new on 4g. + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, + 33L, 27L, 8L, 10L, 1L))); + forcePollAndWaitForIdle(); + + // Verify ALL_MOBILE template gets all. 3g template counters do not increase. + assertUidTotal(sTemplateImsi1, UID_RED, 49L, 49L, 25L, 12L, 1); + assertUidTotal(template3g, UID_RED, 12L, 18L, 14L, 1L, 0); + // Verify 4g template counts appended stats on existing entry and newly created entry. + assertUidTotal(template4g, UID_RED, 4L + 33L, 4L + 27L, 3L + 8L, 1L + 10L, 1); + // Verify 5g template doesn't get anything since no traffic is generated on 5g. + assertUidTotal(template5g, UID_RED, 0L, 0L, 0L, 0L, 0); + + // 5g network comes online. + incrementCurrentTime(MINUTE_IN_MILLIS); + setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_NR); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) + // Existing stats remains. + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, + 16L, 22L, 17L, 2L, 0L)) + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, + 33L, 27L, 8L, 10L, 1L)) + // Add some traffic on 5g. + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, + 5L, 13L, 31L, 9L, 2L))); + forcePollAndWaitForIdle(); + + // Verify ALL_MOBILE template gets all. + assertUidTotal(sTemplateImsi1, UID_RED, 54L, 62L, 56L, 21L, 3); + // 3g/4g template counters do not increase. + assertUidTotal(template3g, UID_RED, 12L, 18L, 14L, 1L, 0); + assertUidTotal(template4g, UID_RED, 4L + 33L, 4L + 27L, 3L + 8L, 1L + 10L, 1); + // Verify 5g template gets the 5g count. + assertUidTotal(template5g, UID_RED, 5L, 13L, 31L, 9L, 2); + } + + // TODO: support per IMSI state + private void setMobileRatTypeAndWaitForIdle(int ratType) { + when(mNetworkStatsSubscriptionsMonitor.getRatTypeForSubscriberId(anyString())) + .thenReturn(ratType); + mService.handleOnCollapsedRatTypeChanged(); + HandlerUtilsKt.waitForIdle(mHandlerThread, WAIT_TIMEOUT); + } + + @Test public void testSummaryForAllUid() throws Exception { // pretend that network comes online expectDefaultSettings(); NetworkState[] states = new NetworkState[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); - expectBandwidthControlCheck(); - mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states)); + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]); // create some traffic for two apps incrementCurrentTime(HOUR_IN_MILLIS); expectDefaultSettings(); expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L) - .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0L)); + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L) + .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0L)); mService.incrementOperationCount(UID_RED, 0xF00D, 1); forcePollAndWaitForIdle(); @@ -614,9 +715,10 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { expectDefaultSettings(); expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L) - .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 2048L, 16L, 1024L, 8L, 0L)); + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L) + .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, + 2048L, 16L, 1024L, 8L, 0L)); forcePollAndWaitForIdle(); // first verify entire history present @@ -646,9 +748,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { NetworkState[] states = new NetworkState[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); - expectBandwidthControlCheck(); - mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states)); + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]); NetworkStats.Entry entry1 = new NetworkStats.Entry( TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L); @@ -661,9 +762,9 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { expectDefaultSettings(); expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3) - .addValues(entry1) - .addValues(entry2) - .addValues(entry3)); + .insertEntry(entry1) + .insertEntry(entry2) + .insertEntry(entry3)); mService.incrementOperationCount(UID_RED, 0xF00D, 1); NetworkStats stats = mService.getDetailedUidStats(INTERFACES_ALL); @@ -690,9 +791,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); - expectBandwidthControlCheck(); - mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states)); + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]); NetworkStats.Entry uidStats = new NetworkStats.Entry( TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xF00D, 1024L, 8L, 512L, 4L, 0L); @@ -704,28 +804,33 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { "otherif", UID_BLUE, SET_DEFAULT, 0xF00D, 1024L, 8L, 512L, 4L, 0L); final String[] ifaceFilter = new String[] { TEST_IFACE }; + final String[] augmentedIfaceFilter = new String[] { stackedIface, TEST_IFACE }; incrementCurrentTime(HOUR_IN_MILLIS); expectDefaultSettings(); expectNetworkStatsSummary(buildEmptyStats()); - when(mNetManager.getNetworkStatsUidDetail(eq(UID_ALL), any())) + when(mStatsFactory.augmentWithStackedInterfaces(eq(ifaceFilter))) + .thenReturn(augmentedIfaceFilter); + when(mStatsFactory.readNetworkStatsDetail(eq(UID_ALL), any(), eq(TAG_ALL))) .thenReturn(new NetworkStats(getElapsedRealtime(), 1) - .addValues(uidStats)); + .insertEntry(uidStats)); when(mNetManager.getNetworkStatsTethering(STATS_PER_UID)) .thenReturn(new NetworkStats(getElapsedRealtime(), 2) - .addValues(tetheredStats1) - .addValues(tetheredStats2)); + .insertEntry(tetheredStats1) + .insertEntry(tetheredStats2)); NetworkStats stats = mService.getDetailedUidStats(ifaceFilter); - // mNetManager#getNetworkStatsUidDetail(UID_ALL, INTERFACES_ALL) has following invocations: + // mStatsFactory#readNetworkStatsDetail() has the following invocations: // 1) NetworkStatsService#systemReady from #setUp. // 2) mService#forceUpdateIfaces in the test above. // // Additionally, we should have one call from the above call to mService#getDetailedUidStats - // with the augmented ifaceFilter - verify(mNetManager, times(2)).getNetworkStatsUidDetail(UID_ALL, INTERFACES_ALL); - verify(mNetManager, times(1)).getNetworkStatsUidDetail( - eq(UID_ALL), eq(NetworkStatsFactory.augmentWithStackedInterfaces(ifaceFilter))); + // with the augmented ifaceFilter. + verify(mStatsFactory, times(2)).readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL); + verify(mStatsFactory, times(1)).readNetworkStatsDetail( + eq(UID_ALL), + eq(augmentedIfaceFilter), + eq(TAG_ALL)); assertTrue(ArrayUtils.contains(stats.getUniqueIfaces(), TEST_IFACE)); assertTrue(ArrayUtils.contains(stats.getUniqueIfaces(), stackedIface)); assertEquals(2, stats.size()); @@ -740,17 +845,16 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { NetworkState[] states = new NetworkState[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); - expectBandwidthControlCheck(); - mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states)); + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]); // create some initial traffic incrementCurrentTime(HOUR_IN_MILLIS); expectDefaultSettings(); expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L)); + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L)); mService.incrementOperationCount(UID_RED, 0xF00D, 1); forcePollAndWaitForIdle(); @@ -764,10 +868,10 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { expectDefaultSettings(); expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 0L)); + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 0L)); mService.setUidForeground(UID_RED, true); mService.incrementOperationCount(UID_RED, 0xFAAD, 1); @@ -797,9 +901,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { NetworkState[] states = new NetworkState[] {buildWifiState(true /* isMetered */)}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); - expectBandwidthControlCheck(); - mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states)); + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]); // create some initial traffic incrementCurrentTime(HOUR_IN_MILLIS); @@ -809,9 +912,9 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { // and DEFAULT_NETWORK_YES, because these three properties aren't tracked at that layer. // We layer them on top by inspecting the iface properties. expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 0L)); mService.incrementOperationCount(UID_RED, 0xF00D, 1); @@ -837,9 +940,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { new NetworkState[] {buildMobile3gState(IMSI_1, true /* isRoaming */)}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); - expectBandwidthControlCheck(); - mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states)); + mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]); // Create some traffic incrementCurrentTime(HOUR_IN_MILLIS); @@ -849,9 +951,9 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { // ROAMING_NO, because metered and roaming isn't tracked at that layer. We layer it // on top by inspecting the iface properties. expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_NO, + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_NO, DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_ALL, ROAMING_NO, + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_ALL, ROAMING_NO, DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 0L)); forcePollAndWaitForIdle(); @@ -875,9 +977,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { NetworkState[] states = new NetworkState[] {buildMobile3gState(IMSI_1)}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); - expectBandwidthControlCheck(); - mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states)); + mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]); // create some tethering traffic incrementCurrentTime(HOUR_IN_MILLIS); @@ -885,17 +986,17 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { // Traffic seen by kernel counters (includes software tethering). final NetworkStats ifaceStats = new NetworkStats(getElapsedRealtime(), 1) - .addIfaceValues(TEST_IFACE, 1536L, 12L, 384L, 3L); + .insertEntry(TEST_IFACE, 1536L, 12L, 384L, 3L); // Hardware tethering traffic, not seen by kernel counters. final NetworkStats tetherStatsHardware = new NetworkStats(getElapsedRealtime(), 1) - .addIfaceValues(TEST_IFACE, 512L, 4L, 128L, 1L); + .insertEntry(TEST_IFACE, 512L, 4L, 128L, 1L); // Traffic for UID_RED. final NetworkStats uidStats = new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L); + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L); // All tethering traffic, both hardware and software. final NetworkStats tetherStats = new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L, + .insertEntry(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L, 0L); expectNetworkStatsSummary(ifaceStats, tetherStatsHardware); @@ -916,9 +1017,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { NetworkState[] states = new NetworkState[] {buildWifiState()}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); - expectBandwidthControlCheck(); - mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states)); + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]); // verify service has empty history for wifi assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0); @@ -936,8 +1036,6 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); - - // Register and verify request and that binder was called DataUsageRequest request = mService.registerUsageCallback(mServiceContext.getOpPackageName(), inputRequest, @@ -947,22 +1045,17 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { long minThresholdInBytes = 2 * 1024 * 1024; // 2 MB assertEquals(minThresholdInBytes, request.thresholdInBytes); - // Send dummy message to make sure that any previous message has been handled - mHandler.sendMessage(mHandler.obtainMessage(-1)); - waitForIdleHandler(mHandler, WAIT_TIMEOUT); - - + HandlerUtilsKt.waitForIdle(mHandlerThread, WAIT_TIMEOUT); // Make sure that the caller binder gets connected verify(mBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt()); - // modify some number on wifi, and trigger poll event // not enough traffic to call data usage callback incrementCurrentTime(HOUR_IN_MILLIS); expectDefaultSettings(); expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) - .addIfaceValues(TEST_IFACE, 1024L, 1L, 2048L, 2L)); + .insertEntry(TEST_IFACE, 1024L, 1L, 2048L, 2L)); expectNetworkStatsUidDetail(buildEmptyStats()); forcePollAndWaitForIdle(); @@ -977,7 +1070,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { incrementCurrentTime(DAY_IN_MILLIS); expectDefaultSettings(); expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) - .addIfaceValues(TEST_IFACE, 4096000L, 4L, 8192000L, 8L)); + .insertEntry(TEST_IFACE, 4096000L, 4L, 8192000L, 8L)); expectNetworkStatsUidDetail(buildEmptyStats()); forcePollAndWaitForIdle(); @@ -1014,6 +1107,90 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { mService.unregisterUsageRequest(unknownRequest); } + @Test + public void testStatsProviderUpdateStats() throws Exception { + // Pretend that network comes online. + expectDefaultSettings(); + final NetworkState[] states = new NetworkState[]{buildWifiState(true /* isMetered */)}; + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + + // Register custom provider and retrieve callback. + final TestableNetworkStatsProviderBinder provider = + new TestableNetworkStatsProviderBinder(); + final INetworkStatsProviderCallback cb = + mService.registerNetworkStatsProvider("TEST", provider); + assertNotNull(cb); + + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]); + + // Verifies that one requestStatsUpdate will be called during iface update. + provider.expectOnRequestStatsUpdate(0 /* unused */); + + // Create some initial traffic and report to the service. + incrementCurrentTime(HOUR_IN_MILLIS); + final NetworkStats expectedStats = new NetworkStats(0L, 1) + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, + TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, + 128L, 2L, 128L, 2L, 1L)) + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, + 0xF00D, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, + 64L, 1L, 64L, 1L, 1L)); + cb.notifyStatsUpdated(0 /* unused */, expectedStats, expectedStats); + + // Make another empty mutable stats object. This is necessary since the new NetworkStats + // object will be used to compare with the old one in NetworkStatsRecoder, two of them + // cannot be the same object. + expectNetworkStatsUidDetail(buildEmptyStats()); + + forcePollAndWaitForIdle(); + + // Verifies that one requestStatsUpdate and setAlert will be called during polling. + provider.expectOnRequestStatsUpdate(0 /* unused */); + provider.expectOnSetAlert(MB_IN_BYTES); + + // Verifies that service recorded history, does not verify uid tag part. + assertUidTotal(sTemplateWifi, UID_RED, 128L, 2L, 128L, 2L, 1); + + // Verifies that onStatsUpdated updates the stats accordingly. + final NetworkStats stats = mSession.getSummaryForAllUid( + sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true); + assertEquals(2, stats.size()); + assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 1L); + assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO, + DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 1L); + + // Verifies that unregister the callback will remove the provider from service. + cb.unregister(); + forcePollAndWaitForIdle(); + provider.assertNoCallback(); + } + + @Test + public void testStatsProviderSetAlert() throws Exception { + // Pretend that network comes online. + expectDefaultSettings(); + NetworkState[] states = new NetworkState[]{buildWifiState(true /* isMetered */)}; + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]); + + // Register custom provider and retrieve callback. + final TestableNetworkStatsProviderBinder provider = + new TestableNetworkStatsProviderBinder(); + final INetworkStatsProviderCallback cb = + mService.registerNetworkStatsProvider("TEST", provider); + assertNotNull(cb); + + // Simulates alert quota of the provider has been reached. + cb.notifyAlertReached(); + HandlerUtilsKt.waitForIdle(mHandlerThread, WAIT_TIMEOUT); + + // Verifies that polling is triggered by alert reached. + provider.expectOnRequestStatsUpdate(0 /* unused */); + // Verifies that global alert will be re-armed. + provider.expectOnSetAlert(MB_IN_BYTES); + } + private static File getBaseDir(File statsDir) { File baseDir = new File(statsDir, "netstats"); baseDir.mkdirs(); @@ -1062,7 +1239,6 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { private void expectSystemReady() throws Exception { expectNetworkStatsSummary(buildEmptyStats()); - expectBandwidthControlCheck(); } private String getActiveIface(NetworkState... states) throws Exception { @@ -1084,11 +1260,11 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { } private void expectNetworkStatsSummaryDev(NetworkStats summary) throws Exception { - when(mNetManager.getNetworkStatsSummaryDev()).thenReturn(summary); + when(mStatsFactory.readNetworkStatsSummaryDev()).thenReturn(summary); } private void expectNetworkStatsSummaryXt(NetworkStats summary) throws Exception { - when(mNetManager.getNetworkStatsSummaryXt()).thenReturn(summary); + when(mStatsFactory.readNetworkStatsSummaryXt()).thenReturn(summary); } private void expectNetworkStatsTethering(int how, NetworkStats stats) @@ -1102,7 +1278,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { private void expectNetworkStatsUidDetail(NetworkStats detail, NetworkStats tetherStats) throws Exception { - when(mNetManager.getNetworkStatsUidDetail(UID_ALL, INTERFACES_ALL)).thenReturn(detail); + when(mStatsFactory.readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL)) + .thenReturn(detail); // also include tethering details, since they are folded into UID when(mNetManager.getNetworkStatsTethering(STATS_PER_UID)).thenReturn(tetherStats); @@ -1115,7 +1292,9 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { private void expectSettings(long persistBytes, long bucketDuration, long deleteAge) throws Exception { when(mSettings.getPollInterval()).thenReturn(HOUR_IN_MILLIS); + when(mSettings.getPollDelay()).thenReturn(0L); when(mSettings.getSampleEnabled()).thenReturn(true); + when(mSettings.getCombineSubtypeEnabled()).thenReturn(false); final Config config = new Config(bucketDuration, deleteAge, deleteAge); when(mSettings.getDevConfig()).thenReturn(config); @@ -1130,10 +1309,6 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { when(mSettings.getUidTagPersistBytes(anyLong())).thenReturn(MB_IN_BYTES); } - private void expectBandwidthControlCheck() throws Exception { - when(mNetManager.isBandwidthControlEnabled()).thenReturn(true); - } - private void assertStatsFilesExist(boolean exist) { final File basePath = new File(mStatsDir, "netstats"); if (exist) { @@ -1165,6 +1340,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { final NetworkCapabilities capabilities = new NetworkCapabilities(); capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, !isMetered); capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true); + capabilities.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); return new NetworkState(info, prop, capabilities, WIFI_NETWORK, null, TEST_SSID); } @@ -1182,10 +1358,11 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { final NetworkCapabilities capabilities = new NetworkCapabilities(); capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, false); capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, !isRoaming); + capabilities.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); return new NetworkState(info, prop, capabilities, MOBILE_NETWORK, subscriberId, null); } - private static NetworkState buildMobile4gState(String iface) { + private static NetworkState buildWimaxState(@NonNull String iface) { final NetworkInfo info = new NetworkInfo(TYPE_WIMAX, 0, null, null); info.setDetailedState(DetailedState.CONNECTED, null, null); final LinkProperties prop = new LinkProperties(); @@ -1226,9 +1403,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { private void forcePollAndWaitForIdle() { mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL)); - // Send dummy message to make sure that any previous message has been handled - mHandler.sendMessage(mHandler.obtainMessage(-1)); - waitForIdleHandler(mHandler, WAIT_TIMEOUT); + HandlerUtilsKt.waitForIdle(mHandlerThread, WAIT_TIMEOUT); } static class LatchedHandler extends Handler { diff --git a/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java b/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java new file mode 100644 index 000000000000..c91dfecf041b --- /dev/null +++ b/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.net; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.content.Context; +import android.net.NetworkTemplate; +import android.os.Looper; +import android.telephony.NetworkRegistrationInfo; +import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; + +import com.android.internal.util.CollectionUtils; +import com.android.server.net.NetworkStatsSubscriptionsMonitor.Delegate; +import com.android.server.net.NetworkStatsSubscriptionsMonitor.RatTypeListener; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +@RunWith(JUnit4.class) +public final class NetworkStatsSubscriptionsMonitorTest { + private static final int TEST_SUBID1 = 3; + private static final int TEST_SUBID2 = 5; + private static final String TEST_IMSI1 = "466921234567890"; + private static final String TEST_IMSI2 = "466920987654321"; + private static final String TEST_IMSI3 = "466929999999999"; + + @Mock private Context mContext; + @Mock private SubscriptionManager mSubscriptionManager; + @Mock private TelephonyManager mTelephonyManager; + @Mock private Delegate mDelegate; + private final List<Integer> mTestSubList = new ArrayList<>(); + + private final Executor mExecutor = Executors.newSingleThreadExecutor(); + private NetworkStatsSubscriptionsMonitor mMonitor; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager); + + when(mContext.getSystemService(eq(Context.TELEPHONY_SUBSCRIPTION_SERVICE))) + .thenReturn(mSubscriptionManager); + when(mContext.getSystemService(eq(Context.TELEPHONY_SERVICE))) + .thenReturn(mTelephonyManager); + + mMonitor = new NetworkStatsSubscriptionsMonitor(mContext, mExecutor, mDelegate); + } + + @Test + public void testStartStop() { + // Verify that addOnSubscriptionsChangedListener() is never called before start(). + verify(mSubscriptionManager, never()) + .addOnSubscriptionsChangedListener(mExecutor, mMonitor); + mMonitor.start(); + verify(mSubscriptionManager).addOnSubscriptionsChangedListener(mExecutor, mMonitor); + + // Verify that removeOnSubscriptionsChangedListener() is never called before stop() + verify(mSubscriptionManager, never()).removeOnSubscriptionsChangedListener(mMonitor); + mMonitor.stop(); + verify(mSubscriptionManager).removeOnSubscriptionsChangedListener(mMonitor); + } + + @NonNull + private static int[] convertArrayListToIntArray(@NonNull List<Integer> arrayList) { + final int[] list = new int[arrayList.size()]; + for (int i = 0; i < arrayList.size(); i++) { + list[i] = arrayList.get(i); + } + return list; + } + + private void setRatTypeForSub(List<RatTypeListener> listeners, + int subId, int type) { + final ServiceState serviceState = mock(ServiceState.class); + when(serviceState.getDataNetworkType()).thenReturn(type); + final RatTypeListener match = CollectionUtils + .find(listeners, it -> it.getSubId() == subId); + if (match != null) { + match.onServiceStateChanged(serviceState); + } + } + + private void addTestSub(int subId, String subscriberId) { + // add SubId to TestSubList. + if (!mTestSubList.contains(subId)) { + mTestSubList.add(subId); + } + final int[] subList = convertArrayListToIntArray(mTestSubList); + when(mSubscriptionManager.getCompleteActiveSubscriptionIdList()).thenReturn(subList); + when(mTelephonyManager.getSubscriberId(subId)).thenReturn(subscriberId); + mMonitor.onSubscriptionsChanged(); + } + + private void removeTestSub(int subId) { + // Remove subId from TestSubList. + mTestSubList.removeIf(it -> it == subId); + final int[] subList = convertArrayListToIntArray(mTestSubList); + when(mSubscriptionManager.getCompleteActiveSubscriptionIdList()).thenReturn(subList); + mMonitor.onSubscriptionsChanged(); + } + + private void assertRatTypeChangedForSub(String subscriberId, int ratType) { + assertEquals(mMonitor.getRatTypeForSubscriberId(subscriberId), ratType); + final ArgumentCaptor<Integer> typeCaptor = ArgumentCaptor.forClass(Integer.class); + // Verify callback with the subscriberId and the RAT type should be as expected. + // It will fail if get a callback with an unexpected RAT type. + verify(mDelegate).onCollapsedRatTypeChanged(eq(subscriberId), typeCaptor.capture()); + final int type = typeCaptor.getValue(); + assertEquals(ratType, type); + } + + private void assertRatTypeNotChangedForSub(String subscriberId, int ratType) { + assertEquals(mMonitor.getRatTypeForSubscriberId(subscriberId), ratType); + // Should never get callback with any RAT type. + verify(mDelegate, never()).onCollapsedRatTypeChanged(eq(subscriberId), anyInt()); + } + + @Test + public void testSubChangedAndRatTypeChanged() { + final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor = + ArgumentCaptor.forClass(RatTypeListener.class); + + mMonitor.start(); + // Insert sim1, verify RAT type is NETWORK_TYPE_UNKNOWN, and never get any callback + // before changing RAT type. + addTestSub(TEST_SUBID1, TEST_IMSI1); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + + // Insert sim2. + addTestSub(TEST_SUBID2, TEST_IMSI2); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + verify(mTelephonyManager, times(2)).listen(ratTypeListenerCaptor.capture(), + eq(PhoneStateListener.LISTEN_SERVICE_STATE)); + reset(mDelegate); + + // Set RAT type of sim1 to UMTS. + // Verify RAT type of sim1 after subscription gets onCollapsedRatTypeChanged() callback + // and others remain untouched. + setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1, + TelephonyManager.NETWORK_TYPE_UMTS); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS); + assertRatTypeNotChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_UNKNOWN); + assertRatTypeNotChangedForSub(TEST_IMSI3, TelephonyManager.NETWORK_TYPE_UNKNOWN); + reset(mDelegate); + + // Set RAT type of sim2 to LTE. + // Verify RAT type of sim2 after subscription gets onCollapsedRatTypeChanged() callback + // and others remain untouched. + setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID2, + TelephonyManager.NETWORK_TYPE_LTE); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS); + assertRatTypeChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_LTE); + assertRatTypeNotChangedForSub(TEST_IMSI3, TelephonyManager.NETWORK_TYPE_UNKNOWN); + reset(mDelegate); + + // Remove sim2 and verify that callbacks are fired and RAT type is correct for sim2. + // while the other two remain untouched. + removeTestSub(TEST_SUBID2); + verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_NONE)); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS); + assertRatTypeChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_UNKNOWN); + assertRatTypeNotChangedForSub(TEST_IMSI3, TelephonyManager.NETWORK_TYPE_UNKNOWN); + reset(mDelegate); + + // Set RAT type of sim1 to UNKNOWN. Then stop monitoring subscription changes + // and verify that the listener for sim1 is removed. + setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1, + TelephonyManager.NETWORK_TYPE_UNKNOWN); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + reset(mDelegate); + + mMonitor.stop(); + verify(mTelephonyManager, times(2)).listen(any(), eq(PhoneStateListener.LISTEN_NONE)); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + } + + + @Test + public void test5g() { + mMonitor.start(); + // Insert sim1, verify RAT type is NETWORK_TYPE_UNKNOWN, and never get any callback + // before changing RAT type. Also capture listener for later use. + addTestSub(TEST_SUBID1, TEST_IMSI1); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor = + ArgumentCaptor.forClass(RatTypeListener.class); + verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor.capture(), + eq(PhoneStateListener.LISTEN_SERVICE_STATE)); + final RatTypeListener listener = CollectionUtils + .find(ratTypeListenerCaptor.getAllValues(), it -> it.getSubId() == TEST_SUBID1); + assertNotNull(listener); + + // Set RAT type to 5G NSA (non-standalone) mode, verify the monitor outputs + // NETWORK_TYPE_5G_NSA. + final ServiceState serviceState = mock(ServiceState.class); + when(serviceState.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_LTE); + when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED); + listener.onServiceStateChanged(serviceState); + assertRatTypeChangedForSub(TEST_IMSI1, NetworkTemplate.NETWORK_TYPE_5G_NSA); + reset(mDelegate); + + // Set RAT type to LTE without NR connected, the RAT type should be downgraded to LTE. + when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_NONE); + listener.onServiceStateChanged(serviceState); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_LTE); + reset(mDelegate); + + // Verify NR connected with other RAT type does not take effect. + when(serviceState.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_UMTS); + when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED); + listener.onServiceStateChanged(serviceState); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS); + reset(mDelegate); + + // Set RAT type to 5G standalone mode, the RAT type should be NR. + setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1, + TelephonyManager.NETWORK_TYPE_NR); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_NR); + reset(mDelegate); + + // Set NR state to none in standalone mode does not change anything. + when(serviceState.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_NR); + when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_NONE); + listener.onServiceStateChanged(serviceState); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_NR); + } +} diff --git a/tests/net/res/raw/xt_qtaguid_vpn_one_underlying_two_vpn b/tests/net/res/raw/xt_qtaguid_vpn_one_underlying_two_vpn new file mode 100644 index 000000000000..eb0513b10049 --- /dev/null +++ b/tests/net/res/raw/xt_qtaguid_vpn_one_underlying_two_vpn @@ -0,0 +1,9 @@ +idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets +2 test_nss_tun0 0x0 1001 0 2000 200 1000 100 0 0 0 0 0 0 0 0 0 0 0 0 +3 test_nss_tun0 0x0 1002 0 1000 100 500 50 0 0 0 0 0 0 0 0 0 0 0 0 +4 test_nss_tun1 0x0 1001 0 3000 300 700 70 0 0 0 0 0 0 0 0 0 0 0 0 +5 test_nss_tun1 0x0 1002 0 500 50 250 25 0 0 0 0 0 0 0 0 0 0 0 0 +6 test0 0x0 1004 0 3300 300 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +7 test0 0x0 1004 1 0 0 1650 150 0 0 0 0 0 0 0 0 0 0 0 0 +8 test1 0x0 1004 0 3850 350 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +9 test1 0x0 1004 1 0 0 1045 95 0 0 0 0 0 0 0 0 0 0 0 0
\ No newline at end of file diff --git a/tests/net/res/raw/xt_qtaguid_with_clat b/tests/net/res/raw/xt_qtaguid_with_clat index 6cd7499545be..f04b32f08332 100644 --- a/tests/net/res/raw/xt_qtaguid_with_clat +++ b/tests/net/res/raw/xt_qtaguid_with_clat @@ -7,7 +7,7 @@ idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packe 7 v4-wlan0 0x0 10060 1 1448660 1041 31192 753 1448660 1041 0 0 0 0 31192 753 0 0 0 0 8 v4-wlan0 0x0 10102 0 9702 16 2870 23 9702 16 0 0 0 0 2870 23 0 0 0 0 9 v4-wlan0 0x0 10102 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -10 wlan0 0x0 0 0 11058671 7892 0 0 11043898 7811 13117 61 1656 20 0 0 0 0 0 0 +10 wlan0 0x0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 11 wlan0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 12 wlan0 0x0 1000 0 6126 13 2013 16 5934 11 192 2 0 0 1821 14 192 2 0 0 13 wlan0 0x0 1000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 @@ -41,5 +41,3 @@ idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packe 41 dummy0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 42 lo 0x0 0 0 1288 16 1288 16 0 0 532 8 756 8 0 0 532 8 756 8 43 lo 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -44 wlan0 0x0 1029 0 0 0 312046 5113 0 0 0 0 0 0 306544 5046 3230 38 2272 29 -45 wlan0 0x0 1029 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
\ No newline at end of file diff --git a/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_after b/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_after index 9f86153a33cf..12d98ca29f57 100644 --- a/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_after +++ b/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_after @@ -9,7 +9,7 @@ idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packe 9 v4-wlan0 0x0 10057 1 728 7 392 7 0 0 728 7 0 0 0 0 392 7 0 0 10 v4-wlan0 0x0 10106 0 2232 18 2232 18 0 0 2232 18 0 0 0 0 2232 18 0 0 11 v4-wlan0 0x0 10106 1 432952718 314238 5442288 121260 432950238 314218 2480 20 0 0 5433900 121029 8388 231 0 0 -12 wlan0 0x0 0 0 440746376 329772 0 0 439660007 315369 232001 1276 854368 13127 0 0 0 0 0 0 +12 wlan0 0x0 0 0 330187296 250652 0 0 329106990 236273 226202 1255 854104 13124 0 0 0 0 0 0 13 wlan0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 14 wlan0 0x0 1000 0 77113 272 56151 575 77113 272 0 0 0 0 19191 190 36960 385 0 0 15 wlan0 0x0 1000 1 20227 80 8356 72 18539 74 1688 6 0 0 7562 66 794 6 0 0 diff --git a/tests/net/res/raw/xt_qtaguid_with_clat_simple b/tests/net/res/raw/xt_qtaguid_with_clat_simple index b37fae6d2a3d..a1d6d411bad8 100644 --- a/tests/net/res/raw/xt_qtaguid_with_clat_simple +++ b/tests/net/res/raw/xt_qtaguid_with_clat_simple @@ -1,5 +1,4 @@ idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets 2 v4-wlan0 0x0 10060 0 42600 213 4100 41 42600 213 0 0 0 0 4100 41 0 0 0 0 3 v4-wlan0 0x0 10060 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -4 wlan0 0x0 0 0 46860 213 0 0 46860 213 0 0 0 0 0 0 0 0 0 0 -5 wlan0 0x0 1029 0 0 0 4920 41 0 0 0 0 0 0 4920 41 0 0 0 0 +4 wlan0 0x0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 diff --git a/tests/net/smoketest/Android.bp b/tests/net/smoketest/Android.bp index ef1ad2cba804..84ae2b5d845e 100644 --- a/tests/net/smoketest/Android.bp +++ b/tests/net/smoketest/Android.bp @@ -14,4 +14,9 @@ android_test { defaults: ["FrameworksNetTests-jni-defaults"], srcs: ["java/SmokeTest.java"], test_suites: ["device-tests"], -} + static_libs: [ + "androidx.test.rules", + "mockito-target-minus-junit4", + "services.core", + ], +}
\ No newline at end of file diff --git a/tests/net/util/java/com/android/internal/util/ParcelableTestUtil.java b/tests/net/util/java/com/android/internal/util/ParcelableTestUtil.java deleted file mode 100644 index 87537b93887b..000000000000 --- a/tests/net/util/java/com/android/internal/util/ParcelableTestUtil.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.util; - -import static org.junit.Assert.assertEquals; - -import java.lang.reflect.Modifier; -import java.util.Arrays; - -/** - * Utility classes to write tests for stable AIDL parceling/unparceling - */ -public final class ParcelableTestUtil { - - /** - * Verifies that the number of nonstatic fields in a class equals a given count. - * - * <p>This assertion serves as a reminder to update test code around it if fields are added - * after the test is written. - * @param count Expected number of nonstatic fields in the class. - * @param clazz Class to test. - */ - public static <T> void assertFieldCountEquals(int count, Class<T> clazz) { - assertEquals(count, Arrays.stream(clazz.getDeclaredFields()) - .filter(f -> !Modifier.isStatic(f.getModifiers())).count()); - } -} diff --git a/tests/net/util/java/com/android/internal/util/TestUtils.java b/tests/net/util/java/com/android/internal/util/TestUtils.java deleted file mode 100644 index a99cd4716f9a..000000000000 --- a/tests/net/util/java/com/android/internal/util/TestUtils.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.util; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -import android.os.ConditionVariable; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Parcel; -import android.os.Parcelable; - -import androidx.annotation.NonNull; - -import java.util.concurrent.Executor; - -public final class TestUtils { - private TestUtils() { } - - /** - * Block until the given Handler thread becomes idle, or until timeoutMs has passed. - */ - public static void waitForIdleHandler(HandlerThread handlerThread, long timeoutMs) { - waitForIdleLooper(handlerThread.getLooper(), timeoutMs); - } - - /** - * Block until the given Looper becomes idle, or until timeoutMs has passed. - */ - public static void waitForIdleLooper(Looper looper, long timeoutMs) { - waitForIdleHandler(new Handler(looper), timeoutMs); - } - - /** - * Block until the given Handler becomes idle, or until timeoutMs has passed. - */ - public static void waitForIdleHandler(Handler handler, long timeoutMs) { - final ConditionVariable cv = new ConditionVariable(); - handler.post(() -> cv.open()); - if (!cv.block(timeoutMs)) { - fail(handler.toString() + " did not become idle after " + timeoutMs + " ms"); - } - } - - /** - * Block until the given Serial Executor becomes idle, or until timeoutMs has passed. - */ - public static void waitForIdleSerialExecutor(@NonNull Executor executor, long timeoutMs) { - final ConditionVariable cv = new ConditionVariable(); - executor.execute(() -> cv.open()); - if (!cv.block(timeoutMs)) { - fail(executor.toString() + " did not become idle after " + timeoutMs + " ms"); - } - } - - /** - * Return a new instance of {@code T} after being parceled then unparceled. - */ - public static <T extends Parcelable> T parcelingRoundTrip(T source) { - final Parcelable.Creator<T> creator; - try { - creator = (Parcelable.Creator<T>) source.getClass().getField("CREATOR").get(null); - } catch (IllegalAccessException | NoSuchFieldException e) { - fail("Missing CREATOR field: " + e.getMessage()); - return null; - } - Parcel p = Parcel.obtain(); - source.writeToParcel(p, /* flags */ 0); - p.setDataPosition(0); - final byte[] marshalled = p.marshall(); - p = Parcel.obtain(); - p.unmarshall(marshalled, 0, marshalled.length); - p.setDataPosition(0); - return creator.createFromParcel(p); - } - - /** - * Assert that after being parceled then unparceled, {@code source} is equal to the original - * object. - */ - public static <T extends Parcelable> void assertParcelingIsLossless(T source) { - assertEquals(source, parcelingRoundTrip(source)); - } -} diff --git a/tests/permission/src/com/android/framework/permission/tests/PmPermissionsTests.java b/tests/permission/src/com/android/framework/permission/tests/PmPermissionsTests.java index b39db61794de..299d8d003d9c 100644 --- a/tests/permission/src/com/android/framework/permission/tests/PmPermissionsTests.java +++ b/tests/permission/src/com/android/framework/permission/tests/PmPermissionsTests.java @@ -43,8 +43,8 @@ public class PmPermissionsTests extends AndroidTestCase { try { mPm.getPackageSizeInfo(mPkgName, null); fail("PackageManager.getPackageSizeInfo" + - "did not throw SecurityException as expected"); - } catch (SecurityException e) { + "did not throw UnsupportedOperationException as expected"); + } catch (UnsupportedOperationException e) { // expected } } @@ -130,4 +130,4 @@ public class PmPermissionsTests extends AndroidTestCase { // expected } } -}
\ No newline at end of file +} diff --git a/tests/permission/src/com/android/framework/permission/tests/ServiceManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/ServiceManagerPermissionTests.java deleted file mode 100644 index dcbbdbbcd320..000000000000 --- a/tests/permission/src/com/android/framework/permission/tests/ServiceManagerPermissionTests.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.framework.permission.tests; - -import com.android.internal.os.BinderInternal; - -import android.app.AppOpsManager; -import android.os.Binder; -import android.os.IPermissionController; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.ServiceManagerNative; -import android.test.suitebuilder.annotation.SmallTest; - -import junit.framework.TestCase; - -/** - * TODO: Remove this. This is only a placeholder, need to implement this. - */ -public class ServiceManagerPermissionTests extends TestCase { - @SmallTest - public void testAddService() { - try { - // The security in the service manager is that you can't replace - // a service that is already published. - Binder binder = new Binder(); - ServiceManager.addService("activity", binder); - fail("ServiceManager.addService did not throw SecurityException as" - + " expected"); - } catch (SecurityException e) { - // expected - } - } - - @SmallTest - public void testSetPermissionController() { - try { - IPermissionController pc = new IPermissionController.Stub() { - @Override - public boolean checkPermission(java.lang.String permission, int pid, int uid) { - return true; - } - - @Override - public int noteOp(String op, int uid, String packageName) { - return AppOpsManager.MODE_ALLOWED; - } - - @Override - public String[] getPackagesForUid(int uid) { - return new String[0]; - } - - @Override - public boolean isRuntimePermission(String permission) { - return false; - } - - @Override - public int getPackageUid(String packageName, int flags) { - return -1; - } - }; - ServiceManagerNative.asInterface(BinderInternal.getContextObject()) - .setPermissionController(pc); - fail("IServiceManager.setPermissionController did not throw SecurityException as" - + " expected"); - } catch (SecurityException e) { - // expected - } catch (RemoteException e) { - fail("Unexpected remote exception"); - } - } -} diff --git a/tests/permission/src/com/android/framework/permission/tests/SmsManagerPermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/SmsManagerPermissionTest.java index 273943fe2d2e..41727431bd5e 100644 --- a/tests/permission/src/com/android/framework/permission/tests/SmsManagerPermissionTest.java +++ b/tests/permission/src/com/android/framework/permission/tests/SmsManagerPermissionTest.java @@ -16,12 +16,12 @@ package com.android.framework.permission.tests; -import java.util.ArrayList; - import android.telephony.SmsManager; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; +import java.util.ArrayList; + /** * Verify that SmsManager apis cannot be called without required permissions. */ @@ -32,6 +32,10 @@ public class SmsManagerPermissionTest extends AndroidTestCase { private static final String DEST_NUMBER = "4567"; private static final String SRC_NUMBER = "1234"; + private static final int CELL_BROADCAST_MESSAGE_ID_START = 10; + private static final int CELL_BROADCAST_MESSAGE_ID_END = 20; + private static final int CELL_BROADCAST_GSM_RAN_TYPE = 0; + /** * Verify that SmsManager.sendTextMessage requires permissions. * <p>Tests Permission: @@ -82,4 +86,34 @@ public class SmsManagerPermissionTest extends AndroidTestCase { // expected } } + + /** + * Verify that SmsManager.enableCellBroadcastRange requires permissions. + * <p>Tests system permission: android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST + */ + @SmallTest + public void testEnableCellBroadcastRange() { + try { + SmsManager.getDefault().enableCellBroadcastRange(CELL_BROADCAST_MESSAGE_ID_START, + CELL_BROADCAST_MESSAGE_ID_END, CELL_BROADCAST_GSM_RAN_TYPE); + fail("SmsManager.sendDataMessage did not throw SecurityException as expected"); + } catch (SecurityException e) { + // expected + } + } + + /** + * Verify that SmsManager.disableCellBroadcastRange requires permissions. + * <p>Tests system permission: android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST + */ + @SmallTest + public void testDisableCellBroadcastRange() { + try { + SmsManager.getDefault().disableCellBroadcastRange(CELL_BROADCAST_MESSAGE_ID_START, + CELL_BROADCAST_MESSAGE_ID_END, CELL_BROADCAST_GSM_RAN_TYPE); + fail("SmsManager.sendDataMessage did not throw SecurityException as expected"); + } catch (SecurityException e) { + // expected + } + } } diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java index c50229ae30f4..d1d6a26790fd 100644 --- a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java +++ b/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java @@ -16,12 +16,12 @@ package com.android.framework.permission.tests; -import android.media.AudioAttributes; import android.os.Binder; import android.os.IVibratorService; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.test.suitebuilder.annotation.SmallTest; @@ -52,8 +52,8 @@ public class VibratorServicePermissionTest extends TestCase { try { final VibrationEffect effect = VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE); - final AudioAttributes attrs = new AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_ALARM) + final VibrationAttributes attrs = new VibrationAttributes.Builder() + .setUsage(VibrationAttributes.USAGE_ALARM) .build(); mVibratorService.vibrate(Process.myUid(), null, effect, attrs, "testVibrate", new Binder()); diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java index 4c2a984f8198..737665fb97e4 100644 --- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java +++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java @@ -17,6 +17,7 @@ package com.android.framework.permission.tests; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import android.os.Binder; import android.os.RemoteException; @@ -27,6 +28,8 @@ import android.view.IWindowManager; import junit.framework.TestCase; +import org.junit.Test; + /** * TODO: Remove this. This is only a placeholder, need to implement this. */ @@ -53,7 +56,7 @@ public class WindowManagerPermissionTests extends TestCase { } try { - mWm.addWindowToken(null, 0, DEFAULT_DISPLAY); + mWm.addWindowToken(null, TYPE_APPLICATION, DEFAULT_DISPLAY); fail("IWindowManager.addWindowToken did not throw SecurityException as" + " expected"); } catch (SecurityException e) { @@ -63,16 +66,6 @@ public class WindowManagerPermissionTests extends TestCase { } try { - mWm.removeWindowToken(null, DEFAULT_DISPLAY); - fail("IWindowManager.removeWindowToken did not throw SecurityException as" - + " expected"); - } catch (SecurityException e) { - // expected - } catch (RemoteException e) { - fail("Unexpected remote exception"); - } - - try { mWm.prepareAppTransition(0, false); fail("IWindowManager.prepareAppTransition did not throw SecurityException as" + " expected"); @@ -182,4 +175,29 @@ public class WindowManagerPermissionTests extends TestCase { fail("Unexpected remote exception"); } } + + @Test + public void testADD_WINDOW_TOKEN_WITH_OPTIONS() { + // Verify if addWindowTokenWithOptions throw SecurityException for privileged window type. + try { + mWm.addWindowTokenWithOptions(null, TYPE_APPLICATION, DEFAULT_DISPLAY, null, ""); + fail("IWindowManager.addWindowTokenWithOptions did not throw SecurityException as" + + " expected"); + } catch (SecurityException e) { + // expected + } catch (RemoteException e) { + fail("Unexpected remote exception"); + } + + // Verify if addWindowTokenWithOptions throw SecurityException for null packageName. + try { + mWm.addWindowTokenWithOptions(null, TYPE_APPLICATION, DEFAULT_DISPLAY, null, null); + fail("IWindowManager.addWindowTokenWithOptions did not throw SecurityException as" + + " expected"); + } catch (SecurityException e) { + // expected + } catch (RemoteException e) { + fail("Unexpected remote exception"); + } + } } diff --git a/tests/privapp-permissions/Android.bp b/tests/privapp-permissions/Android.bp index ca7864f047c7..b6618508d95d 100644 --- a/tests/privapp-permissions/Android.bp +++ b/tests/privapp-permissions/Android.bp @@ -45,17 +45,17 @@ prebuilt_etc { } android_app { - name: "ProductServicesPrivAppPermissionTest", + name: "SystemExtPrivAppPermissionTest", sdk_version: "current", privileged: true, - manifest: "product_services/AndroidManifest.xml", - product_services_specific: true, - required: ["product_servicesprivapp-permissions-test.xml"], + manifest: "system_ext/AndroidManifest.xml", + system_ext_specific: true, + required: ["system_extprivapp-permissions-test.xml"], } prebuilt_etc { - name: "product_servicesprivapp-permissions-test.xml", - src: "product_services/privapp-permissions-test.xml", + name: "system_extprivapp-permissions-test.xml", + src: "system_ext/privapp-permissions-test.xml", sub_dir: "permissions", - product_services_specific: true, + system_ext_specific: true, } diff --git a/tests/privapp-permissions/product_services/AndroidManifest.xml b/tests/privapp-permissions/system_ext/AndroidManifest.xml index 511ddee729ca..4a0e82f1b599 100644 --- a/tests/privapp-permissions/product_services/AndroidManifest.xml +++ b/tests/privapp-permissions/system_ext/AndroidManifest.xml @@ -16,7 +16,7 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.framework.permission.privapp.tests.product_services"> + package="com.android.framework.permission.privapp.tests.system_ext"> <!-- MANAGE_USB is signature|privileged --> <uses-permission android:name="android.permission.MANAGE_USB"/> diff --git a/tests/privapp-permissions/product_services/privapp-permissions-test.xml b/tests/privapp-permissions/system_ext/privapp-permissions-test.xml index 43baebbb0aad..ad63e8643e97 100644 --- a/tests/privapp-permissions/product_services/privapp-permissions-test.xml +++ b/tests/privapp-permissions/system_ext/privapp-permissions-test.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <permissions> - <privapp-permissions package="com.android.framework.permission.privapp.tests.product_services"> + <privapp-permissions package="com.android.framework.permission.privapp.tests.system_ext"> <permission name="android.permission.MANAGE_USB"/> </privapp-permissions> </permissions> diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java index 8d99ac7100eb..ebe9b5706bf8 100644 --- a/tests/testables/src/android/testing/TestableLooper.java +++ b/tests/testables/src/android/testing/TestableLooper.java @@ -222,6 +222,10 @@ public class TestableLooper { return sLoopers.get(test); } + public static void remove(Object test) { + sLoopers.remove(test); + } + static class LooperFrameworkMethod extends FrameworkMethod { private HandlerThread mHandlerThread; @@ -234,6 +238,9 @@ public class TestableLooper { try { mLooper = setAsMain ? Looper.getMainLooper() : createLooper(); mTestableLooper = new TestableLooper(mLooper, false); + if (!setAsMain) { + mTestableLooper.getLooper().getThread().setName(test.getClass().getName()); + } } catch (Exception e) { throw new RuntimeException(e); } diff --git a/tests/touchlag/Android.bp b/tests/touchlag/Android.bp deleted file mode 100644 index 092eea918b1d..000000000000 --- a/tests/touchlag/Android.bp +++ /dev/null @@ -1,14 +0,0 @@ -cc_test { - name: "test-touchlag", - gtest: false, - srcs: ["touchlag.cpp"], - shared_libs: [ - "libcutils", - "libutils", - ], - cflags: [ - "-Wall", - "-Wextra", - "-Werror", - ], -} diff --git a/tests/touchlag/touchlag.cpp b/tests/touchlag/touchlag.cpp deleted file mode 100644 index 9264a254ed24..000000000000 --- a/tests/touchlag/touchlag.cpp +++ /dev/null @@ -1,294 +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. - */ - -#include <stdint.h> -#include <sys/types.h> - -#include <fcntl.h> -#include <sys/ioctl.h> -#include <linux/fb.h> -#include <linux/input.h> -#include <errno.h> -#include <string.h> -#include <stdio.h> -#include <cutils/memory.h> -#include <asm-generic/mman.h> -#include <sys/mman.h> -#include <utils/threads.h> -#include <unistd.h> -#include <math.h> - -using namespace android; - -#ifndef FBIO_WAITFORVSYNC -#define FBIO_WAITFORVSYNC _IOW('F', 0x20, __u32) -#endif - -struct Buffer { - size_t w; - size_t h; - size_t s; - union { - void* addr; - uint32_t* pixels; - }; -}; - -void clearBuffer(Buffer* buf, uint32_t pixel) { - android_memset32(buf->pixels, pixel, buf->s * buf->h * 4); -} - -void drawTwoPixels(Buffer* buf, uint32_t pixel, ssize_t x, ssize_t y, size_t w) { - if (y>0 && y<ssize_t(buf->h)) { - uint32_t* bits = buf->pixels + y * buf->s; - if (x>=0 && x<ssize_t(buf->w)) { - bits[x] = pixel; - } - ssize_t W(w); - if ((x+W)>=0 && (x+W)<ssize_t(buf->w)) { - bits[x+W] = pixel; - } - } -} - -void drawHLine(Buffer* buf, uint32_t pixel, ssize_t x, ssize_t y, size_t w) { - if (y>0 && y<ssize_t(buf->h)) { - ssize_t W(w); - if (x<0) { - W += x; - x = 0; - } - if (x+w > buf->w) { - W = buf->w - x; - } - if (W>0) { - uint32_t* bits = buf->pixels + y * buf->s + x; - android_memset32(bits, pixel, W*4); - } - } -} - -void drawRect(Buffer* buf, uint32_t pixel, ssize_t x, ssize_t y, size_t w, size_t h) { - ssize_t W(w), H(h); - if (x<0) { - w += x; - x = 0; - } - if (y<0) { - h += y; - y = 0; - } - if (x+w > buf->w) W = buf->w - x; - if (y+h > buf->h) H = buf->h - y; - if (W>0 && H>0) { - uint32_t* bits = buf->pixels + y * buf->s + x; - for (ssize_t i=0 ; i<H ; i++) { - android_memset32(bits, pixel, W*4); - bits += buf->s; - } - } -} - -void drawCircle(Buffer* buf, uint32_t pixel, - size_t x0, size_t y0, size_t radius, bool filled = false) { - ssize_t f = 1 - radius; - ssize_t ddF_x = 1; - ssize_t ddF_y = -2 * radius; - ssize_t x = 0; - ssize_t y = radius; - if (filled) { - drawHLine(buf, pixel, x0-radius, y0, 2*radius); - } else { - drawTwoPixels(buf, pixel, x0-radius, y0, 2*radius); - } - while (x < y) { - if (f >= 0) { - y--; - ddF_y += 2; - f += ddF_y; - } - x++; - ddF_x += 2; - f += ddF_x; - if (filled) { - drawHLine(buf, pixel, x0-x, y0+y, 2*x); - drawHLine(buf, pixel, x0-x, y0-y, 2*x); - drawHLine(buf, pixel, x0-y, y0+x, 2*y); - drawHLine(buf, pixel, x0-y, y0-x, 2*y); - } else { - drawTwoPixels(buf, pixel, x0-x, y0+y, 2*x); - drawTwoPixels(buf, pixel, x0-x, y0-y, 2*x); - drawTwoPixels(buf, pixel, x0-y, y0+x, 2*y); - drawTwoPixels(buf, pixel, x0-y, y0-x, 2*y); - } - } -} - -class TouchEvents { - class EventThread : public Thread { - int fd; - - virtual bool threadLoop() { - input_event event; - int first_down = 0; - do { - read(fd, &event, sizeof(event)); - if (event.type == EV_ABS) { - if (event.code == ABS_MT_TRACKING_ID) { - down = event.value == -1 ? 0 : 1; - first_down = down; - } - if (event.code == ABS_MT_POSITION_X) { - x = event.value; - } - if (event.code == ABS_MT_POSITION_Y) { - y = event.value; - } - } - } while (event.type == EV_SYN); - return true; - } - - public: - int x, y, down; - EventThread() : Thread(false), - x(0), y(0), down(0) - { - fd = open("/dev/input/event1", O_RDONLY); - } -}; - sp<EventThread> thread; - -public: - TouchEvents() { - thread = new EventThread(); - thread->run("EventThread", PRIORITY_URGENT_DISPLAY); - } - - int getMostRecentPosition(int* x, int* y) { - *x = thread->x; - *y = thread->y; - return thread->down; - } -}; - - -struct Queue { - struct position { - int x, y; - }; - int index; - position q[16]; - Queue() : index(0) { } - void push(int x, int y) { - index++; - index &= 0xF; - q[index].x = x; - q[index].y = y; - } - void get(int lag, int* x, int* y) { - const int i = (index - lag) & 0xF; - *x = q[i].x; - *y = q[i].y; - } -}; - -extern char *optarg; -extern int optind; -extern int optopt; -extern int opterr; -extern int optreset; - -void usage(const char* name) { - printf("\nusage: %s [-h] [-l lag]\n", name); -} - -int main(int argc, char** argv) { - fb_var_screeninfo vi; - fb_fix_screeninfo fi; - - int lag = 0; - int fd = open("/dev/graphics/fb0", O_RDWR); - ioctl(fd, FBIOGET_VSCREENINFO, &vi); - ioctl(fd, FBIOGET_FSCREENINFO, &fi); - void* bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - Buffer framebuffer; - framebuffer.w = vi.xres; - framebuffer.h = vi.yres; - framebuffer.s = fi.line_length / (vi.bits_per_pixel >> 3); - framebuffer.addr = bits; - - int ch; - while ((ch = getopt(argc, argv, "hl:")) != -1) { - switch (ch) { - case 'l': - lag = atoi(optarg); - break; - case 'h': - default: - usage(argv[0]); - exit(0); - } - } - argc -= optind; - argv += optind; - - - TouchEvents touch; - Queue queue; - - - int x=0, y=0; - int lag_x=0, lag_y=0; - - clearBuffer(&framebuffer, 0); - while (true) { - uint32_t crt = 0; - ioctl(fd, FBIO_WAITFORVSYNC, &crt); - - // draw beam marker - drawRect(&framebuffer, 0x400000, framebuffer.w-2, 0, 2, framebuffer.h); - // erase screen - if (lag) { - drawCircle(&framebuffer, 0, lag_x, lag_y, 100); - drawHLine(&framebuffer, 0, 0, lag_y, 32); - } - drawCircle(&framebuffer, 0, x, y, 100, true); - drawHLine(&framebuffer, 0, 0, y, 32); - - // draw a line at y=1000 - drawHLine(&framebuffer, 0x808080, 0, 1000, framebuffer.w); - - // get touch events - touch.getMostRecentPosition(&x, &y); - queue.push(x, y); - queue.get(lag, &lag_x, &lag_y); - - if (lag) { - drawCircle(&framebuffer, 0x00FF00, lag_x, lag_y, 100); - drawHLine(&framebuffer, 0x00FF00, 0, lag_y, 32); - } - - drawCircle(&framebuffer, 0xFFFFFF, x, y, 100, true); - drawHLine(&framebuffer, 0xFFFFFF, 0, y, 32); - - // draw end of frame beam marker - drawRect(&framebuffer, 0x004000, framebuffer.w-2, 0, 2, framebuffer.h); - } - - close(fd); - return 0; -} diff --git a/tests/utils/hostutils/Android.bp b/tests/utils/hostutils/Android.bp new file mode 100644 index 000000000000..c9ad70280aa6 --- /dev/null +++ b/tests/utils/hostutils/Android.bp @@ -0,0 +1,28 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +java_library_host { + name: "frameworks-base-hostutils", + + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + + libs: [ + "tradefed", + "junit", + ], +} diff --git a/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java b/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java new file mode 100644 index 000000000000..f30c35aca8da --- /dev/null +++ b/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java @@ -0,0 +1,365 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util.test; + +import static org.junit.Assert.assertTrue; + +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.log.LogUtil; + +import org.junit.Assert; +import org.junit.ClassRule; +import org.junit.rules.ExternalResource; +import org.junit.rules.TemporaryFolder; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; + +import javax.annotation.Nullable; + +/** + * Allows pushing files onto the device and various options for rebooting. Useful for installing + * APKs/files to system partitions which otherwise wouldn't be easily changed. + * + * It's strongly recommended to pass in a {@link ClassRule} annotated {@link TestRuleDelegate} to + * do a full reboot at the end of a test to ensure the device is in a valid state, assuming the + * default {@link RebootStrategy#FULL} isn't used. + */ +public class SystemPreparer extends ExternalResource { + private static final long OVERLAY_ENABLE_TIMEOUT_MS = 30000; + + // The paths of the files pushed onto the device through this rule. + private ArrayList<String> mPushedFiles = new ArrayList<>(); + + // The package names of packages installed through this rule. + private ArrayList<String> mInstalledPackages = new ArrayList<>(); + + private final TemporaryFolder mHostTempFolder; + private final DeviceProvider mDeviceProvider; + private final RebootStrategy mRebootStrategy; + private final TearDownRule mTearDownRule; + + public SystemPreparer(TemporaryFolder hostTempFolder, DeviceProvider deviceProvider) { + this(hostTempFolder, RebootStrategy.FULL, null, deviceProvider); + } + + public SystemPreparer(TemporaryFolder hostTempFolder, RebootStrategy rebootStrategy, + @Nullable TestRuleDelegate testRuleDelegate, DeviceProvider deviceProvider) { + mHostTempFolder = hostTempFolder; + mDeviceProvider = deviceProvider; + mRebootStrategy = rebootStrategy; + mTearDownRule = new TearDownRule(mDeviceProvider); + if (testRuleDelegate != null) { + testRuleDelegate.setDelegate(mTearDownRule); + } + } + + /** Copies a file within the host test jar to a path on device. */ + public SystemPreparer pushResourceFile(String filePath, String outputPath) + throws DeviceNotAvailableException, IOException { + final ITestDevice device = mDeviceProvider.getDevice(); + remount(); + assertTrue(device.pushFile(copyResourceToTemp(filePath), outputPath)); + mPushedFiles.add(outputPath); + return this; + } + + /** Copies a file directly from the host file system to a path on device. */ + public SystemPreparer pushFile(File file, String outputPath) + throws DeviceNotAvailableException { + final ITestDevice device = mDeviceProvider.getDevice(); + remount(); + assertTrue(device.pushFile(file, outputPath)); + mPushedFiles.add(outputPath); + return this; + } + + /** Deletes the given path from the device */ + public SystemPreparer deleteFile(String file) throws DeviceNotAvailableException { + final ITestDevice device = mDeviceProvider.getDevice(); + remount(); + device.deleteFile(file); + return this; + } + + /** Installs an APK within the host test jar onto the device. */ + public SystemPreparer installResourceApk(String resourcePath, String packageName) + throws DeviceNotAvailableException, IOException { + final ITestDevice device = mDeviceProvider.getDevice(); + final File tmpFile = copyResourceToTemp(resourcePath); + final String result = device.installPackage(tmpFile, true /* reinstall */); + Assert.assertNull(result); + mInstalledPackages.add(packageName); + return this; + } + + /** Sets the enable state of an overlay package. */ + public SystemPreparer setOverlayEnabled(String packageName, boolean enabled) + throws DeviceNotAvailableException { + final ITestDevice device = mDeviceProvider.getDevice(); + final String enable = enabled ? "enable" : "disable"; + + // Wait for the overlay to change its enabled state. + final long endMillis = System.currentTimeMillis() + OVERLAY_ENABLE_TIMEOUT_MS; + String result; + while (System.currentTimeMillis() <= endMillis) { + device.executeShellCommand(String.format("cmd overlay %s %s", enable, packageName)); + result = device.executeShellCommand("cmd overlay dump isenabled " + + packageName); + if (((enabled) ? "true\n" : "false\n").equals(result)) { + return this; + } + + try { + Thread.sleep(200); + } catch (InterruptedException ignore) { + } + } + + throw new IllegalStateException(String.format("Failed to %s overlay %s:\n%s", enable, + packageName, device.executeShellCommand("cmd overlay list"))); + } + + /** Restarts the device and waits until after boot is completed. */ + public SystemPreparer reboot() throws DeviceNotAvailableException { + ITestDevice device = mDeviceProvider.getDevice(); + switch (mRebootStrategy) { + case FULL: + device.reboot(); + break; + case UNTIL_ONLINE: + device.rebootUntilOnline(); + break; + case USERSPACE: + device.rebootUserspace(); + break; + case USERSPACE_UNTIL_ONLINE: + device.rebootUserspaceUntilOnline(); + break; + case START_STOP: + device.executeShellCommand("stop"); + device.executeShellCommand("start"); + ITestDevice.RecoveryMode cachedRecoveryMode = device.getRecoveryMode(); + device.setRecoveryMode(ITestDevice.RecoveryMode.ONLINE); + + if (device.isEncryptionSupported()) { + if (device.isDeviceEncrypted()) { + LogUtil.CLog.e("Device is encrypted after userspace reboot!"); + device.unlockDevice(); + } + } + + device.setRecoveryMode(cachedRecoveryMode); + device.waitForDeviceAvailable(); + break; + } + return this; + } + + public SystemPreparer remount() throws DeviceNotAvailableException { + mTearDownRule.remount(); + return this; + } + + /** Copies a file within the host test jar to a temporary file on the host machine. */ + private File copyResourceToTemp(String resourcePath) throws IOException { + final File tempFile = mHostTempFolder.newFile(); + final ClassLoader classLoader = getClass().getClassLoader(); + try (InputStream assetIs = classLoader.getResource(resourcePath).openStream(); + FileOutputStream assetOs = new FileOutputStream(tempFile)) { + if (assetIs == null) { + throw new IllegalStateException("Failed to find resource " + resourcePath); + } + + int b; + while ((b = assetIs.read()) >= 0) { + assetOs.write(b); + } + } + + return tempFile; + } + + /** Removes installed packages and files that were pushed to the device. */ + @Override + protected void after() { + final ITestDevice device = mDeviceProvider.getDevice(); + try { + remount(); + for (final String file : mPushedFiles) { + device.deleteFile(file); + } + for (final String packageName : mInstalledPackages) { + device.uninstallPackage(packageName); + } + reboot(); + } catch (DeviceNotAvailableException e) { + Assert.fail(e.toString()); + } + } + + /** + * A hacky workaround since {@link org.junit.AfterClass} and {@link ClassRule} require static + * members. Will defer assignment of the actual {@link TestRule} to execute until after any + * test case has been run. + * + * In effect, this makes the {@link ITestDevice} to be accessible after all test cases have + * been executed, allowing {@link ITestDevice#reboot()} to be used to fully restore the device. + */ + public static class TestRuleDelegate implements TestRule { + + private boolean mThrowOnNull; + + @Nullable + private TestRule mTestRule; + + public TestRuleDelegate(boolean throwOnNull) { + mThrowOnNull = throwOnNull; + } + + public void setDelegate(TestRule testRule) { + mTestRule = testRule; + } + + @Override + public Statement apply(Statement base, Description description) { + if (mTestRule == null) { + if (mThrowOnNull) { + throw new IllegalStateException("TestRule delegate was not set"); + } else { + return new Statement() { + @Override + public void evaluate() throws Throwable { + base.evaluate(); + } + }; + } + } + + Statement statement = mTestRule.apply(base, description); + mTestRule = null; + return statement; + } + } + + /** + * Forces a full reboot at the end of the test class to restore any device state. + */ + private static class TearDownRule extends ExternalResource { + + private DeviceProvider mDeviceProvider; + private boolean mInitialized; + private boolean mWasVerityEnabled; + private boolean mWasAdbRoot; + private boolean mIsVerityEnabled; + + TearDownRule(DeviceProvider deviceProvider) { + mDeviceProvider = deviceProvider; + } + + @Override + protected void before() { + // This method will never be run + } + + @Override + protected void after() { + try { + initialize(); + ITestDevice device = mDeviceProvider.getDevice(); + if (mWasVerityEnabled != mIsVerityEnabled) { + device.executeShellCommand( + mWasVerityEnabled ? "enable-verity" : "disable-verity"); + } + device.reboot(); + if (!mWasAdbRoot) { + device.disableAdbRoot(); + } + } catch (DeviceNotAvailableException e) { + Assert.fail(e.toString()); + } + } + + /** + * Remount is done inside this class so that the verity state can be tracked. + */ + public void remount() throws DeviceNotAvailableException { + initialize(); + ITestDevice device = mDeviceProvider.getDevice(); + device.enableAdbRoot(); + if (mIsVerityEnabled) { + mIsVerityEnabled = false; + device.executeShellCommand("disable-verity"); + device.reboot(); + } + device.executeShellCommand("remount"); + device.waitForDeviceAvailable(); + } + + private void initialize() throws DeviceNotAvailableException { + if (mInitialized) { + return; + } + mInitialized = true; + ITestDevice device = mDeviceProvider.getDevice(); + mWasAdbRoot = device.isAdbRoot(); + device.enableAdbRoot(); + String veritySystem = device.getProperty("partition.system.verified"); + String verityVendor = device.getProperty("partition.vendor.verified"); + mWasVerityEnabled = (veritySystem != null && !veritySystem.isEmpty()) + || (verityVendor != null && !verityVendor.isEmpty()); + mIsVerityEnabled = mWasVerityEnabled; + } + } + + public interface DeviceProvider { + ITestDevice getDevice(); + } + + /** + * How to reboot the device. Ordered from slowest to fastest. + */ + public enum RebootStrategy { + /** @see ITestDevice#reboot() */ + FULL, + + /** @see ITestDevice#rebootUntilOnline() () */ + UNTIL_ONLINE, + + /** @see ITestDevice#rebootUserspace() */ + USERSPACE, + + /** @see ITestDevice#rebootUserspaceUntilOnline() () */ + USERSPACE_UNTIL_ONLINE, + + /** + * Uses shell stop && start to "reboot" the device. May leave invalid state after each test. + * Whether this matters or not depends on what's being tested. + * + * TODO(b/159540015): There's a bug with this causing unnecessary disk space usage, which + * can eventually lead to an insufficient storage space error. + */ + START_STOP + } +} diff --git a/tests/utils/hostutils/src/com/android/tests/rollback/host/AbandonSessionsRule.java b/tests/utils/hostutils/src/com/android/tests/rollback/host/AbandonSessionsRule.java new file mode 100644 index 000000000000..b08621314ee0 --- /dev/null +++ b/tests/utils/hostutils/src/com/android/tests/rollback/host/AbandonSessionsRule.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tests.rollback.host; + +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import org.junit.rules.ExternalResource; + +public class AbandonSessionsRule extends ExternalResource { + private final BaseHostJUnit4Test mHost; + + public AbandonSessionsRule(BaseHostJUnit4Test host) { + mHost = host; + } + + @Override + protected void before() throws Throwable { + abandonSessions(mHost.getDevice()); + } + + @Override + protected void after() { + try { + abandonSessions(mHost.getDevice()); + } catch (Exception ignore) { + } + } + + /** + * Abandons all sessions to prevent interference in our tests. + */ + private static void abandonSessions(ITestDevice device) throws Exception { + // No point in abandoning applied or failed sessions. We care about ready sessions only. + String cmdListReadySessions = + "pm list staged-sessions --only-sessionid --only-parent --only-ready"; + String output = device.executeShellCommand(cmdListReadySessions); + if (output.trim().isEmpty()) { + // No sessions to abandon + return; + } + // Ensure we have sufficient privilege to abandon sessions from other apps + device.enableAdbRoot(); + device.executeShellCommand("for i in $(" + cmdListReadySessions + + "); do pm install-abandon $i; done"); + device.disableAdbRoot(); + } +} diff --git a/tests/utils/testutils/Android.bp b/tests/utils/testutils/Android.bp index f71be7b0b7d3..a6625ab9c17f 100644 --- a/tests/utils/testutils/Android.bp +++ b/tests/utils/testutils/Android.bp @@ -22,6 +22,7 @@ java_library { static_libs: [ "junit", "hamcrest-library", + "androidx.test.runner", ], libs: [ diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java index a49eda3d86d0..a826646f69f3 100644 --- a/tests/utils/testutils/java/android/os/test/TestLooper.java +++ b/tests/utils/testutils/java/android/os/test/TestLooper.java @@ -48,6 +48,8 @@ public class TestLooper { private static final Method MESSAGE_MARK_IN_USE_METHOD; private static final String TAG = "TestLooper"; + private final Clock mClock; + private AutoDispatchThread mAutoDispatchThread; static { @@ -69,8 +71,25 @@ public class TestLooper { } } - + /** + * Creates a TestLooper and installs it as the looper for the current thread. + */ public TestLooper() { + this(SystemClock::uptimeMillis); + } + + /** + * Creates a TestLooper with a custom clock and installs it as the looper for the current + * thread. + * + * Messages are dispatched when their {@link Message#when} is before or at {@link + * Clock#uptimeMillis()}. + * Use a custom clock with care. When using an offsettable clock like {@link + * com.android.server.testutils.OffsettableClock} be sure not to double offset messages by + * offsetting the clock and calling {@link #moveTimeForward(long)}. Instead, offset the clock + * and call {@link #dispatchAll()}. + */ + public TestLooper(Clock clock) { try { mLooper = LOOPER_CONSTRUCTOR.newInstance(false); @@ -80,6 +99,8 @@ public class TestLooper { } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { throw new RuntimeException("Reflection error constructing or accessing looper", e); } + + mClock = clock; } public Looper getLooper() { @@ -116,9 +137,13 @@ public class TestLooper { } } + private long currentTime() { + return mClock.uptimeMillis(); + } + private Message messageQueueNext() { try { - long now = SystemClock.uptimeMillis(); + long now = currentTime(); Message prevMsg = null; Message msg = getMessageLinkedList(); @@ -157,7 +182,7 @@ public class TestLooper { public synchronized boolean isIdle() { Message messageList = getMessageLinkedList(); - return messageList != null && SystemClock.uptimeMillis() >= messageList.getWhen(); + return messageList != null && currentTime() >= messageList.getWhen(); } /** @@ -187,6 +212,7 @@ public class TestLooper { /** * Dispatch all messages currently in the queue * Will not fail if there are no messages pending + * * @return the number of messages dispatched */ public synchronized int dispatchAll() { @@ -198,6 +224,10 @@ public class TestLooper { return count; } + public interface Clock { + long uptimeMillis(); + } + /** * Thread used to dispatch messages when the main thread is blocked waiting for a response. */ @@ -210,33 +240,36 @@ public class TestLooper { /** * Run method for the auto dispatch thread. * The thread loops a maximum of MAX_LOOPS times with a 10ms sleep between loops. - * The thread continues looping and attempting to dispatch all messages until at - * least one message has been dispatched. + * The thread continues looping and attempting to dispatch all messages until + * {@link #stopAutoDispatch()} has been invoked. */ @Override public void run() { int dispatchCount = 0; for (int i = 0; i < MAX_LOOPS; i++) { try { - dispatchCount = dispatchAll(); + dispatchCount += dispatchAll(); } catch (RuntimeException e) { mAutoDispatchException = e; - } - Log.d(TAG, "dispatched " + dispatchCount + " messages"); - if (dispatchCount > 0) { return; } + Log.d(TAG, "dispatched " + dispatchCount + " messages"); try { Thread.sleep(LOOP_SLEEP_TIME_MS); } catch (InterruptedException e) { - mAutoDispatchException = new IllegalStateException( - "stopAutoDispatch called before any messages were dispatched."); + if (dispatchCount == 0) { + Log.e(TAG, "stopAutoDispatch called before any messages were dispatched."); + mAutoDispatchException = new IllegalStateException( + "stopAutoDispatch called before any messages were dispatched."); + } return; } } - Log.e(TAG, "AutoDispatchThread did not dispatch any messages."); - mAutoDispatchException = new IllegalStateException( - "TestLooper did not dispatch any messages before exiting."); + if (dispatchCount == 0) { + Log.e(TAG, "AutoDispatchThread did not dispatch any messages."); + mAutoDispatchException = new IllegalStateException( + "TestLooper did not dispatch any messages before exiting."); + } } /** @@ -287,4 +320,17 @@ public class TestLooper { "stopAutoDispatch called without startAutoDispatch."); } } + + /** + * If an AutoDispatchThread is currently running, stop and clean up. + * This method ignores exceptions raised for indicating that no messages were dispatched. + */ + public void stopAutoDispatchAndIgnoreExceptions() { + try { + stopAutoDispatch(); + } catch (IllegalStateException e) { + Log.e(TAG, "stopAutoDispatch", e); + } + + } } diff --git a/tests/utils/testutils/java/android/view/test/InsetsModeSession.java b/tests/utils/testutils/java/android/view/test/InsetsModeSession.java index c83dfa41d260..e05fdce0ca0c 100644 --- a/tests/utils/testutils/java/android/view/test/InsetsModeSession.java +++ b/tests/utils/testutils/java/android/view/test/InsetsModeSession.java @@ -31,7 +31,7 @@ public class InsetsModeSession implements AutoCloseable { } @Override - public void close() throws Exception { + public void close() { ViewRootImpl.sNewInsetsMode = mOldMode; } } diff --git a/tests/utils/testutils/java/com/android/server/accessibility/test/MessageCapturingHandler.java b/tests/utils/testutils/java/com/android/server/accessibility/test/MessageCapturingHandler.java new file mode 100644 index 000000000000..bce2ab5c5a7f --- /dev/null +++ b/tests/utils/testutils/java/com/android/server/accessibility/test/MessageCapturingHandler.java @@ -0,0 +1,118 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accessibility.test; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Pair; + +import androidx.test.InstrumentationRegistry; + +import java.util.ArrayList; +import java.util.List; + +/** + * Utility class to capture messages dispatched through a handler and control when they arrive + * at their target. + */ +public class MessageCapturingHandler extends Handler { + public List<Pair<Message, Long>> timedMessages = new ArrayList<>(); + + Handler.Callback mCallback; + + public MessageCapturingHandler(Handler.Callback callback) { + this(InstrumentationRegistry.getContext().getMainLooper(), callback); + } + + public MessageCapturingHandler(Looper looper, Callback callback) { + super(looper); + mCallback = callback; + } + + /** + * Holding messages in queue, but never dispatching. + * @see #removeAllMessages() + */ + @Override + public boolean sendMessageAtTime(Message message, long uptimeMillis) { + timedMessages.add(new Pair<>(Message.obtain(message), uptimeMillis)); + return super.sendMessageAtTime(message, Long.MAX_VALUE); + } + + public void setCallback(Handler.Callback callback) { + mCallback = callback; + } + + public void sendOneMessage() { + Message message = timedMessages.remove(0).first; + removeMessages(message.what, message.obj); + dispatchMessage(message); + removeStaleMessages(); + } + + public void sendAllMessages() { + while (!timedMessages.isEmpty()) { + sendOneMessage(); + } + } + + public void sendLastMessage() { + Message message = timedMessages.remove(timedMessages.size() - 1).first; + removeMessages(message.what, message.obj); + dispatchMessage(message); + removeStaleMessages(); + } + + /** + * Clear messages sent from this handler in queue. + * <p> + * If main looper is used, this method should be called in tear down function + * to ensure messages isolation between test cases. + * </p> + */ + public void removeAllMessages() { + if (hasMessages()) { + for (int i = 0; i < timedMessages.size(); i++) { + Message message = timedMessages.get(i).first; + removeMessages(message.what, message.obj); + } + } + } + + public boolean hasMessages() { + removeStaleMessages(); + return !timedMessages.isEmpty(); + } + + private void removeStaleMessages() { + for (int i = 0; i < timedMessages.size(); i++) { + Message message = timedMessages.get(i).first; + if (!hasMessages(message.what, message.obj)) { + timedMessages.remove(i--); + } + } + } + + public void dispatchMessage(Message m) { + if (mCallback != null) { + mCallback.handleMessage(m); + return; + } + super.dispatchMessage(m); + } +} diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java index c9e3404e0f1a..ea803f2aba8b 100644 --- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java +++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java @@ -38,12 +38,18 @@ public final class FrameworksTestsFilter extends SelectTest { "android.app.activity.ActivityThreadClientTest", // Test specifications for FrameworksCoreTests. "android.app.servertransaction.", // all tests under the package. + "android.view.CutoutSpecificationTest", "android.view.DisplayCutoutTest", "android.view.InsetsAnimationControlImplTest", "android.view.InsetsControllerTest", + "android.view.InsetsFlagsTest", "android.view.InsetsSourceTest", "android.view.InsetsSourceConsumerTest", "android.view.InsetsStateTest", + "android.view.WindowMetricsTest", + "android.view.PendingInsetsControllerTest", + "android.app.WindowContextTest", + "android.window.WindowMetricsHelperTest" }; public FrameworksTestsFilter(Bundle testArgs) { |