diff options
author | Svet Ganov <svetoslavganov@google.com> | 2017-05-08 22:57:25 +0000 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2017-05-08 22:57:25 +0000 |
commit | 6dc25a7294804507d1d1191f11dbc11fa4023b45 (patch) | |
tree | cc8bdfb3e229e05157a976377d0abab16a0c98d3 | |
parent | 4dc488454fdc9268acf669f41f25d2c182dc4fe5 (diff) | |
parent | 02af8248abecb6cc93c891831a13bbadbf6dd84a (diff) | |
download | experimental-6dc25a7294804507d1d1191f11dbc11fa4023b45.tar.gz |
My AutoFill test apps am: 560a7ca909
am: 02af8248ab
Change-Id: I07de50421fb44fb95b4ea76cbf642f4dca249acf
30 files changed, 996 insertions, 12 deletions
diff --git a/.DS_Store b/.DS_Store Binary files differnew file mode 100644 index 0000000..5008ddf --- /dev/null +++ b/.DS_Store diff --git a/FillService/Android.mk b/FillService/Android.mk new file mode 100644 index 0000000..9c1231c --- /dev/null +++ b/FillService/Android.mk @@ -0,0 +1,11 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := FillService + +include $(BUILD_PACKAGE) diff --git a/FillService/AndroidManifest.xml b/FillService/AndroidManifest.xml new file mode 100644 index 0000000..f9128e1 --- /dev/null +++ b/FillService/AndroidManifest.xml @@ -0,0 +1,20 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="foo.bar.fill" > + + <application> + + <service android:name=".FillService" + android:permission="android.permission.BIND_AUTOFILL"> + <intent-filter> + <action android:name="android.service.autofill.AutofillService" /> + </intent-filter> + </service> + + <activity android:name=".AuthActivity"/> + <activity android:name=".FirstActivity"/> + <activity android:name=".SecondActivity"/> + + </application> + +</manifest> + diff --git a/FillService/res/drawable-hdpi/ic_launcher.png b/FillService/res/drawable-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..a301d57 --- /dev/null +++ b/FillService/res/drawable-hdpi/ic_launcher.png diff --git a/FillService/res/drawable-ldpi/ic_launcher.png b/FillService/res/drawable-ldpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..2c2a58b --- /dev/null +++ b/FillService/res/drawable-ldpi/ic_launcher.png diff --git a/FillService/res/drawable-mdpi/ic_launcher.png b/FillService/res/drawable-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..f91f736 --- /dev/null +++ b/FillService/res/drawable-mdpi/ic_launcher.png diff --git a/FillService/res/drawable-xhdpi/ic_launcher.png b/FillService/res/drawable-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..96095ec --- /dev/null +++ b/FillService/res/drawable-xhdpi/ic_launcher.png diff --git a/FillService/res/layout/activity_main.xml b/FillService/res/layout/activity_main.xml new file mode 100644 index 0000000..84ffe4b --- /dev/null +++ b/FillService/res/layout/activity_main.xml @@ -0,0 +1,23 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + tools:context=".AuthActivity" + android:orientation="vertical" + android:gravity="center"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="fill_parent" + android:gravity="center"> + + <Button + android:id="@+id/confirm" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/confirm"> + </Button> + + </LinearLayout> + +</LinearLayout> diff --git a/FillService/res/layout/list_item.xml b/FillService/res/layout/list_item.xml new file mode 100644 index 0000000..0d2fa77 --- /dev/null +++ b/FillService/res/layout/list_item.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:background="#ffffffff"> + + <TextView + android:id="@+id/text1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceListItemSmall" + android:gravity="center_vertical" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:minHeight="?android:attr/listPreferredItemHeightSmall"> + </TextView> + + <TextView + android:id="@+id/text2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceListItemSmall" + android:gravity="center_vertical" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:minHeight="?android:attr/listPreferredItemHeightSmall"> + </TextView> + +</LinearLayout> diff --git a/FillService/res/values/strings.xml b/FillService/res/values/strings.xml new file mode 100644 index 0000000..eae7351 --- /dev/null +++ b/FillService/res/values/strings.xml @@ -0,0 +1,9 @@ +<resources> + + <string name="app_name">Filled App</string> + <string name="title_activity_main">MainActivity</string> + <string name="username">Username</string> + <string name="password">Password</string> + <string name="confirm">Confirm</string> + +</resources>
\ No newline at end of file diff --git a/FillService/src/foo/bar/fill/AuthActivity.java b/FillService/src/foo/bar/fill/AuthActivity.java new file mode 100644 index 0000000..101141d --- /dev/null +++ b/FillService/src/foo/bar/fill/AuthActivity.java @@ -0,0 +1,80 @@ +package foo.bar.fill; + +import android.annotation.Nullable; +import android.app.Activity; +import android.app.assist.AssistStructure; +import android.app.assist.AssistStructure.ViewNode; +import android.content.Intent; +import android.os.Bundle; +import android.service.autofill.Dataset; +import android.service.autofill.FillResponse; +import android.view.View; +import android.view.autofill.AutofillManager; +import android.view.autofill.AutofillValue; +import android.widget.Button; +import android.widget.RemoteViews; + +public class AuthActivity extends Activity { + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_main); + + AssistStructure structure = getIntent().getParcelableExtra( + AutofillManager.EXTRA_ASSIST_STRUCTURE); + + ViewNode username = FillService.findUsername(structure); + ViewNode password = FillService.findPassword(structure); + + final FillResponse response; + final Dataset dataset; + + if (FillService.TEST_RESPONSE_AUTH) { + RemoteViews presentation1 = new RemoteViews(getPackageName(), R.layout.list_item); + presentation1.setTextViewText(R.id.text1,FillService.DATASET1_NAME); + + RemoteViews presentation2 = new RemoteViews(getPackageName(), R.layout.list_item); + presentation2.setTextViewText(R.id.text1,FillService.DATASET2_NAME); + + response = new FillResponse.Builder() + .addDataset(new Dataset.Builder(presentation1) + .setValue(username.getAutofillId(), + AutofillValue.forText(FillService.DATASET1_USERNAME)) + .setValue(password.getAutofillId(), + AutofillValue.forText(FillService.DATASET1_PASSWORD)) + .build()) + .addDataset(new Dataset.Builder(presentation2) + .setValue(username.getAutofillId(), + AutofillValue.forText(FillService.DATASET2_USERNAME)) + .setValue(password.getAutofillId(), + AutofillValue.forText(FillService.DATASET2_PASSWORD)) + .build()) + .build(); + dataset = null; + } else { + RemoteViews presentation = new RemoteViews(getPackageName(), R.layout.list_item); + presentation.setTextViewText(R.id.text1,FillService.DATASET2_NAME); + + dataset = new Dataset.Builder(presentation) + .setValue(username.getAutofillId(), + AutofillValue.forText(FillService.DATASET5_USERNAME)) + .setValue(password.getAutofillId(), + AutofillValue.forText(FillService.DATASET5_PASSWORD)) + .build(); + response = null; + } + + Button button = (Button) findViewById(R.id.confirm); + button.setOnClickListener((View v) -> { + Intent result = new Intent(); + if (FillService.TEST_RESPONSE_AUTH) { + result.putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, response); + } else { + result.putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, dataset); + } + setResult(RESULT_OK, result); + finish(); + }); + } +} diff --git a/FillService/src/foo/bar/fill/FillService.java b/FillService/src/foo/bar/fill/FillService.java new file mode 100644 index 0000000..7666ed8 --- /dev/null +++ b/FillService/src/foo/bar/fill/FillService.java @@ -0,0 +1,234 @@ +/* + * 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 foo.bar.fill; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.PendingIntent; +import android.app.assist.AssistStructure; +import android.app.assist.AssistStructure.WindowNode; +import android.app.assist.AssistStructure.ViewNode; +import android.content.Intent; +import android.content.IntentSender; +import android.os.CancellationSignal; +import android.service.autofill.AutofillService; +import android.service.autofill.Dataset; +import android.service.autofill.FillCallback; +import android.service.autofill.FillRequest; +import android.service.autofill.FillResponse; +import android.service.autofill.SaveCallback; +import android.service.autofill.SaveInfo; +import android.service.autofill.SaveRequest; +import android.view.View; +import android.view.autofill.AutofillId; +import android.view.autofill.AutofillValue; +import android.widget.RemoteViews; + +import java.util.function.Predicate; + +import foo.bar.fill.R; + +public class FillService extends AutofillService { + static final boolean TEST_RESPONSE_AUTH = true; + + public static final String RESPONSE_ID = "RESPONSE_ID"; + + static final String DATASET1_NAME = "Foo"; + static final String DATASET1_USERNAME = "Foo"; + static final String DATASET1_PASSWORD = "1"; + + static final String DATASET2_NAME = "Bar"; + static final String DATASET2_USERNAME = "Bar"; + static final String DATASET2_PASSWORD = "12"; + + static final String DATASET3_NAME = "Baz"; + static final String DATASET3_USERNAME = "Baz"; + static final String DATASET3_PASSWORD = "123"; + + static final String DATASET4_NAME = "Bam"; + static final String DATASET4_USERNAME = "Bam"; + static final String DATASET4_PASSWORD = "1234"; + + static final String DATASET5_NAME = "Bak"; + static final String DATASET5_USERNAME = "Bak"; + static final String DATASET5_PASSWORD = "12345"; + + static final String EXTRA_RESPONSE_ID = "foo.bar.fill.extra.RESPONSE_ID"; + static final String EXTRA_DATASET_ID = "foo.bar.fill.extra.DATASET_ID"; + + @Override + public void onFillRequest(@NonNull FillRequest request, + @NonNull CancellationSignal cancellationSignal, + @NonNull FillCallback callback) { + AssistStructure structure = request.getStructure(); + + ViewNode username = findUsername(structure); + ViewNode password = findPassword(structure); + + if (username != null && password != null) { + final FillResponse response; + + if (TEST_RESPONSE_AUTH) { + Intent intent = new Intent(this, AuthActivity.class); + intent.putExtra(EXTRA_RESPONSE_ID, RESPONSE_ID); + IntentSender sender = PendingIntent.getActivity(this, 0, intent, + PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT) + .getIntentSender(); + + RemoteViews presentation = new RemoteViews(getPackageName(), R.layout.list_item); + + presentation.setTextViewText(R.id.text1, "First"); + Intent firstIntent = new Intent(this, FirstActivity.class); + presentation.setOnClickPendingIntent(R.id.text1, PendingIntent.getActivity( + this, 0, firstIntent, PendingIntent.FLAG_CANCEL_CURRENT)); + + presentation.setTextViewText(R.id.text2, "Second"); + Intent secondIntent = new Intent(this, SecondActivity.class); + presentation.setOnClickPendingIntent(R.id.text2, PendingIntent.getActivity( + this, 0, secondIntent, PendingIntent.FLAG_CANCEL_CURRENT)); + + response = new FillResponse.Builder() + .setAuthentication(sender, presentation) + .build(); + } else { + Intent intent = new Intent(this, AuthActivity.class); + intent.putExtra(EXTRA_DATASET_ID, DATASET1_NAME); + IntentSender sender = PendingIntent.getActivity(this, 0, intent, + PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT) + .getIntentSender(); + + RemoteViews presentation1 = new RemoteViews(getPackageName(), R.layout.list_item); + presentation1.setTextViewText(R.id.text1, DATASET1_NAME); + + RemoteViews presentation2 = new RemoteViews(getPackageName(), R.layout.list_item); + presentation2.setTextViewText(R.id.text1, DATASET2_NAME); + + RemoteViews presentation3 = new RemoteViews(getPackageName(), R.layout.list_item); + presentation3.setTextViewText(R.id.text1, DATASET3_NAME); + + RemoteViews presentation4 = new RemoteViews(getPackageName(), R.layout.list_item); + presentation4.setTextViewText(R.id.text1, DATASET4_NAME); + + RemoteViews presentation5 = new RemoteViews(getPackageName(), R.layout.list_item); + presentation5.setTextViewText(R.id.text1, DATASET5_NAME); + + response = new FillResponse.Builder() + .addDataset(new Dataset.Builder(presentation1) + .setValue(username.getAutofillId(), + AutofillValue.forText(DATASET1_USERNAME)) + .setValue(password.getAutofillId(), + AutofillValue.forText(DATASET1_PASSWORD)) + .build()) + .addDataset(new Dataset.Builder(presentation2) + .setValue(username.getAutofillId(), + AutofillValue.forText(DATASET2_USERNAME)) + .setValue(password.getAutofillId(), + AutofillValue.forText(DATASET2_PASSWORD)) +// .setAuthentication(sender) + .build()) + .addDataset(new Dataset.Builder(presentation3) + .setValue(username.getAutofillId(), + AutofillValue.forText(DATASET3_USERNAME)) + .setValue(password.getAutofillId(), + AutofillValue.forText(DATASET3_PASSWORD)) +// .setAuthentication(sender) + .build()) + .addDataset(new Dataset.Builder(presentation4) + .setValue(username.getAutofillId(), + AutofillValue.forText(DATASET4_USERNAME)) + .setValue(password.getAutofillId(), + AutofillValue.forText(DATASET4_PASSWORD)) +// .setAuthentication(sender) + .build()) + .addDataset(new Dataset.Builder(presentation5) + .setValue(username.getAutofillId(), + AutofillValue.forText(DATASET5_USERNAME)) + .setValue(password.getAutofillId(), + AutofillValue.forText(DATASET5_PASSWORD)) + .setAuthentication(sender) + .build()) + .setSaveInfo(new SaveInfo.Builder( + SaveInfo.SAVE_DATA_TYPE_PASSWORD + | SaveInfo.SAVE_DATA_TYPE_USERNAME, + new AutofillId[] {username.getAutofillId(), + password.getAutofillId()}) + .build()) + .build(); + } + + callback.onSuccess(response); + } else { + callback.onFailure("Whoops"); + } + } + + @Override + public void onSaveRequest(@NonNull SaveRequest request, @NonNull SaveCallback callback) { + AssistStructure structure = request.getFillContexts().get(0).getStructure(); + ViewNode username = findUsername(structure); + ViewNode password = findPassword(structure); + } + + static ViewNode findUsername(AssistStructure structure) { + return findByPredicate(structure, (node) -> + node.getAutofillType() == View.AUTOFILL_TYPE_TEXT + && "username".equals(node.getIdEntry()) + ); + } + + static ViewNode findPassword(AssistStructure structure) { + return findByPredicate(structure, (node) -> + node.getAutofillType() == View.AUTOFILL_TYPE_TEXT + && "password".equals(node.getIdEntry()) + ); + } + + private static ViewNode findByPredicate(AssistStructure structure, + Predicate<ViewNode> predicate) { + final int windowCount = structure.getWindowNodeCount(); + for (int i = 0; i < windowCount; i++) { + WindowNode window = structure.getWindowNodeAt(i); + ViewNode root = window.getRootViewNode(); + if (root == null) { + return null; + } + ViewNode node = findByPredicate(root, predicate); + if (node != null) { + return node; + } + } + return null; + } + + private static ViewNode findByPredicate(ViewNode root, Predicate<ViewNode> predicate) { + if (root == null) { + return null; + } + if (predicate.test(root)) { + return root; + } + final int childCount = root.getChildCount(); + for (int i = 0; i < childCount; i++) { + ViewNode child = root.getChildAt(i); + ViewNode node = findByPredicate(child, predicate); + if (node != null) { + return node; + } + } + return null; + } +} diff --git a/FillService/src/foo/bar/fill/FirstActivity.java b/FillService/src/foo/bar/fill/FirstActivity.java new file mode 100644 index 0000000..0132c15 --- /dev/null +++ b/FillService/src/foo/bar/fill/FirstActivity.java @@ -0,0 +1,6 @@ +package foo.bar.fill; + +import android.app.Activity; + +public class FirstActivity extends Activity { +} diff --git a/FillService/src/foo/bar/fill/SecondActivity.java b/FillService/src/foo/bar/fill/SecondActivity.java new file mode 100644 index 0000000..6dea891 --- /dev/null +++ b/FillService/src/foo/bar/fill/SecondActivity.java @@ -0,0 +1,6 @@ +package foo.bar.fill; + +import android.app.Activity; + +public class SecondActivity extends Activity { +} diff --git a/FilledApp/Android.mk b/FilledApp/Android.mk new file mode 100644 index 0000000..2d3c308 --- /dev/null +++ b/FilledApp/Android.mk @@ -0,0 +1,13 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := FilledApp + +LOCAL_PROGUARD_ENABLED := disabled + +include $(BUILD_PACKAGE) diff --git a/FilledApp/AndroidManifest.xml b/FilledApp/AndroidManifest.xml new file mode 100644 index 0000000..383be7e --- /dev/null +++ b/FilledApp/AndroidManifest.xml @@ -0,0 +1,18 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="foo.bar.filled" > + + <application + android:icon="@drawable/ic_launcher" + android:label="@string/app_name" + android:allowBackup="false" > + <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> + </activity> + </application> + +</manifest> diff --git a/FilledApp/res/drawable-hdpi/ic_launcher.png b/FilledApp/res/drawable-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..a301d57 --- /dev/null +++ b/FilledApp/res/drawable-hdpi/ic_launcher.png diff --git a/FilledApp/res/drawable-ldpi/ic_launcher.png b/FilledApp/res/drawable-ldpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..2c2a58b --- /dev/null +++ b/FilledApp/res/drawable-ldpi/ic_launcher.png diff --git a/FilledApp/res/drawable-mdpi/ic_launcher.png b/FilledApp/res/drawable-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..f91f736 --- /dev/null +++ b/FilledApp/res/drawable-mdpi/ic_launcher.png diff --git a/FilledApp/res/drawable-xhdpi/ic_launcher.png b/FilledApp/res/drawable-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..96095ec --- /dev/null +++ b/FilledApp/res/drawable-xhdpi/ic_launcher.png diff --git a/FilledApp/res/layout/activity_main.xml b/FilledApp/res/layout/activity_main.xml new file mode 100644 index 0000000..7bf5e18 --- /dev/null +++ b/FilledApp/res/layout/activity_main.xml @@ -0,0 +1,107 @@ +<foo.bar.filled.CustomLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/content" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".MainActivity" + android:orientation="vertical" > + + <ScrollView + android:layout_width="fill_parent" + android:layout_height="wrap_content"> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <View + android:layout_width="fill_parent" + android:layout_height="500dip"> + </View> + + <HorizontalScrollView + android:layout_width="fill_parent" + android:layout_height="wrap_content"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="fill_parent" + android:orientation="horizontal"> + + <View + android:layout_width="500dip" + android:layout_height="fill_parent"> + </View> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <TextView + android:id="@+id/username_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/username"> + </TextView> + + <EditText + android:id="@+id/username" + android:layout_width="200dip" + android:layout_height="wrap_content"> + </EditText> + + </LinearLayout> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <TextView + android:id="@+id/password_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/password"> + </TextView> + + <EditText + android:id="@+id/password" + android:layout_width="200dip" + android:layout_height="wrap_content"> + </EditText> + + </LinearLayout> + + </LinearLayout> + + <View + android:layout_width="500dip" + android:layout_height="fill_parent"> + </View> + + </LinearLayout> + + </HorizontalScrollView> + + <Button + android:id="@+id/finish" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Finish"> + </Button> + + <View + android:layout_width="fill_parent" + android:layout_height="500dip"> + </View> + + </LinearLayout> + + </ScrollView> + +</foo.bar.filled.CustomLinearLayout> diff --git a/FilledApp/res/values/strings.xml b/FilledApp/res/values/strings.xml new file mode 100644 index 0000000..691778f --- /dev/null +++ b/FilledApp/res/values/strings.xml @@ -0,0 +1,8 @@ +<resources> + + <string name="app_name">Filled App</string> + <string name="title_activity_main">MainActivity</string> + <string name="username">Username</string> + <string name="password">Password</string> + +</resources>
\ No newline at end of file diff --git a/FilledApp/src/foo/bar/filled/CustomLinearLayout.java b/FilledApp/src/foo/bar/filled/CustomLinearLayout.java new file mode 100644 index 0000000..a2fffed --- /dev/null +++ b/FilledApp/src/foo/bar/filled/CustomLinearLayout.java @@ -0,0 +1,143 @@ +package foo.bar.filled; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Rect; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewStructure; +import android.view.autofill.AutofillManager; +import android.view.autofill.AutofillValue; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; + +public class CustomLinearLayout extends LinearLayout { + static final boolean VIRTUAL = false; + + public CustomLinearLayout(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + if (VIRTUAL) { + getViewTreeObserver().addOnGlobalFocusChangeListener((oldFocus, newFocus) -> { + AutofillManager autofillManager = getContext().getSystemService( + AutofillManager.class); + if (oldFocus != null) { + autofillManager.notifyViewExited(CustomLinearLayout.this, + oldFocus.getAccessibilityViewId()); + } + if (newFocus != null) { + Rect bounds = new Rect(); + newFocus.getBoundsOnScreen(bounds); + autofillManager.notifyViewEntered(CustomLinearLayout.this, + newFocus.getAccessibilityViewId(), bounds); + } + }); + } + } + + @Override + public void dispatchProvideAutofillStructure(ViewStructure structure, int flags) { + if (!VIRTUAL) { + super.dispatchProvideAutofillStructure(structure, flags); + } else { + onProvideAutofillVirtualStructure(structure, flags); + } + } + + @Override + public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) { + if (!VIRTUAL) { + return; + } + populateViewStructure(this, structure); + onProvideAutofillVirtualStructureRecursive(this, structure); + } + + @Override + public void autofill(SparseArray<AutofillValue> values) { + final int valueCount = values.size(); + for (int i = 0; i < valueCount; i++) { + final int virtualId = values.keyAt(i); + final AutofillValue value = values.valueAt(i); + View view = findViewByAccessibilityIdTraversal(virtualId); + if (view instanceof EditText && !TextUtils.isEmpty(value.getTextValue())) { + EditText editText = (EditText) view; + editText.setText(value.getTextValue()); + + } + } + } + + private void onProvideAutofillVirtualStructureRecursive(View view, ViewStructure node) { + if (node == null) { + return; + } + if (view instanceof ViewGroup) { + ViewGroup viewGroup = (ViewGroup) view; + final int childCount = viewGroup.getChildCount(); + node.setChildCount(childCount); + for (int i = 0; i < childCount; i++) { + View child = viewGroup.getChildAt(i); + ViewStructure chlidNode = node.newChild(i); + chlidNode.setAutofillId(node, child.getAccessibilityViewId()); + populateViewStructure(child, chlidNode); + onProvideAutofillVirtualStructureRecursive(child, chlidNode); + } + } + } + + private void populateViewStructure(View view, ViewStructure structure) { + if (view.getId() != NO_ID) { + String pkg, type, entry; + try { + final Resources res = getResources(); + entry = res.getResourceEntryName(view.getId()); + type = res.getResourceTypeName(view.getId()); + pkg = res.getResourcePackageName(view.getId()); + } catch (Resources.NotFoundException e) { + entry = type = pkg = null; + } + structure.setId(view.getId(), pkg, type, entry); + } else { + structure.setId(view.getId(), null, null, null); + } + Rect rect = structure.getTempRect(); + view.getDrawingRect(rect); + structure.setDimens(rect.left, rect.top, 0, 0, rect.width(), rect.height()); + structure.setVisibility(VISIBLE); + structure.setEnabled(view.isEnabled()); + if (view.isClickable()) { + structure.setClickable(true); + } + if (view.isFocusable()) { + structure.setFocusable(true); + } + if (view.isFocused()) { + structure.setFocused(true); + } + if (view.isAccessibilityFocused()) { + structure.setAccessibilityFocused(true); + } + if (view.isSelected()) { + structure.setSelected(true); + } + if (view.isLongClickable()) { + structure.setLongClickable(true); + } + CharSequence cname = view.getClass().getName(); + structure.setClassName(cname != null ? cname.toString() : null); + structure.setContentDescription(view.getContentDescription()); + if (view instanceof TextView) { + TextView textView = (TextView) view; + structure.setText(textView.getText(), textView.getSelectionStart(), + textView.getSelectionEnd()); + } + structure.setAutofillHints(view.getAutofillHints()); + structure.setAutofillType(view.getAutofillType()); + structure.setAutofillValue(view.getAutofillValue()); + } +} diff --git a/FilledApp/src/foo/bar/filled/MainActivity.java b/FilledApp/src/foo/bar/filled/MainActivity.java new file mode 100644 index 0000000..f260f37 --- /dev/null +++ b/FilledApp/src/foo/bar/filled/MainActivity.java @@ -0,0 +1,54 @@ +/* + * 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 foo.bar.filled; + +import android.annotation.NonNull; +import android.app.Activity; +import android.os.Bundle; +import android.view.View; +import android.view.autofill.AutofillManager; +import android.widget.Button; + +import foo.bar.filled.R; + +public class MainActivity extends Activity { + public static final String LOG_TAG = "PrintActivity"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + if (!CustomLinearLayout.VIRTUAL) { + findViewById(R.id.username).setImportantForAutofill( + View.IMPORTANT_FOR_AUTOFILL_AUTO); + findViewById(R.id.password).setImportantForAutofill( + View.IMPORTANT_FOR_AUTOFILL_AUTO); + } + + Button finishButton = findViewById(R.id.finish); + finishButton.setOnClickListener((view) -> finish()); + + AutofillManager autofillManager = getSystemService(AutofillManager.class); + autofillManager.registerCallback(new AutofillManager.AutofillCallback() { + @Override + public void onAutofillEvent(@NonNull View view, int event) { + super.onAutofillEvent(view, event); + } + }); + } +} diff --git a/InstantCookieApp/Android.mk b/InstantCookieApp/Android.mk new file mode 100644 index 0000000..233e9a4 --- /dev/null +++ b/InstantCookieApp/Android.mk @@ -0,0 +1,33 @@ +# +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_STATIC_JAVA_LIBRARIES := android-support-test + +LOCAL_PACKAGE_NAME := CtsInstantCookieApp2 + +LOCAL_PROGUARD_ENABLED := disabled + +LOCAL_DEX_PREOPT := false + +include $(BUILD_PACKAGE) diff --git a/InstantCookieApp/AndroidManifest.xml b/InstantCookieApp/AndroidManifest.xml new file mode 100644 index 0000000..339422f --- /dev/null +++ b/InstantCookieApp/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="test.instant.cookie.mokie" + android:versionCode="1" + android:versionName="1.0"> + + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> + + <application/> + + <instrumentation + android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="test.instant.cookie.mokie" /> + +</manifest> diff --git a/InstantCookieApp/src/test/instant/cookie/CookieTest.java b/InstantCookieApp/src/test/instant/cookie/CookieTest.java new file mode 100644 index 0000000..4cc6f86 --- /dev/null +++ b/InstantCookieApp/src/test/instant/cookie/CookieTest.java @@ -0,0 +1,112 @@ +/* + * 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.instant.cookie; + +import android.content.pm.PackageManager; +import android.os.Debug; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +@RunWith(AndroidJUnit4.class) +public class CookieTest { + @Test + public void testCookieUpdateAndRetrieval() throws Exception { + Debug.waitForDebugger(); + PackageManager pm = InstrumentationRegistry.getContext().getPackageManager(); + + // We should be an instant app + assertTrue(pm.isInstantApp()); + + // The max cookie size is greater than zero + assertTrue(pm.getInstantAppCookieMaxSize() > 0); + + // Initially there is no cookie + byte[] cookie = pm.getInstantAppCookie(); + assertTrue(cookie != null && cookie.length == 0); + + // Setting a cookie below max size should work + assertTrue(pm.setInstantAppCookie("1".getBytes())); + +// // Setting a cookie above max size should not work +// assertFalse(pm.setInstantAppCookie( +// new byte[pm.getInstantAppCookieMaxSize() + 1])); +// +// // Ensure cookie not modified +// assertEquals("1", new String(pm.getInstantAppCookie())); + } + +// @Test +// public void testCookiePersistedAcrossInstantInstalls1() throws Exception { +// PackageManager pm = InstrumentationRegistry.getContext().getPackageManager(); +// +// // Set a cookie to later check when reinstalled as instant app +// assertTrue(pm.setInstantAppCookie("2".getBytes())); +// } +// +// @Test +// public void testCookiePersistedAcrossInstantInstalls2() throws Exception { +// PackageManager pm = InstrumentationRegistry.getContext().getPackageManager(); +// +// // After the upgrade the cookie should be the same +// assertEquals("2", new String(pm.getInstantAppCookie())); +// } +// +// @Test +// public void testCookiePersistedUpgradeFromInstant1() throws Exception { +// PackageManager pm = InstrumentationRegistry.getContext().getPackageManager(); +// +// // Make sure we are an instant app +// assertTrue(pm.isInstantApp()); +// +// // Set a cookie to later check when upgrade to a normal app +// assertTrue(pm.setInstantAppCookie("3".getBytes())); +// } +// +// @Test +// public void testCookiePersistedUpgradeFromInstant2() throws Exception { +// PackageManager pm = InstrumentationRegistry.getContext().getPackageManager(); +// +// // Make sure we are not an instant app +// assertFalse(pm.isInstantApp()); +// +// // The cookie survives the upgrade to a normal app +// assertEquals("3", new String(pm.getInstantAppCookie())); +// } +// +// @Test +// public void testCookieResetOnNonInstantReinstall1() throws Exception { +// PackageManager pm = InstrumentationRegistry.getContext().getPackageManager(); +// +// // Set a cookie to later check when reinstalled as normal app +// assertTrue(pm.setInstantAppCookie("4".getBytes())); +// } +// +// @Test +// public void testCookieResetOnNonInstantReinstall2() throws Exception { +// PackageManager pm = InstrumentationRegistry.getContext().getPackageManager(); +// +// // The cookie should have been wiped if non-instant app is uninstalled +// byte[] cookie = pm.getInstantAppCookie(); +// assertTrue(cookie != null && cookie.length == 0); +// } +} diff --git a/PermissionApp/Android.mk b/PermissionApp/Android.mk index a3ada83..00170ca 100644 --- a/PermissionApp/Android.mk +++ b/PermissionApp/Android.mk @@ -8,4 +8,6 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_PACKAGE_NAME := PermissionApp +#LOCAL_CERTIFICATE := platform + include $(BUILD_PACKAGE) diff --git a/PermissionApp/AndroidManifest.xml b/PermissionApp/AndroidManifest.xml index 9c298df..8f35b34 100644 --- a/PermissionApp/AndroidManifest.xml +++ b/PermissionApp/AndroidManifest.xml @@ -3,11 +3,18 @@ android:versionCode="1" android:versionName="1.0"> - <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="10000"/> + <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="22"/> <uses-permission android:name="android.permission.READ_CONTACTS" /> + <uses-permission android:name="android.permission.WRITE_CONTACTS" /> <uses-permission android:name="android.permission.READ_CALENDAR" /> + <uses-permission android:name="android.permission.READ_PHONE_STATE"/> + <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/> + + <uses-permission android:name="android.permission.RUN_IN_BACKGROUND" /> + <uses-permission android:name="android.permission.USE_DATA_IN_BACKGROUND" /> + <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" @@ -21,6 +28,10 @@ <category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LEANBACK_LAUNCHER" /> </intent-filter> + <intent-filter> + <action android:name="android.intent.action.View" /> + <category android:name="android.intent.category.BROWSABLE" /> + </intent-filter> </activity> </application> diff --git a/PermissionApp/src/foo/bar/permission/PermissionActivity.java b/PermissionApp/src/foo/bar/permission/PermissionActivity.java index 75eefca..529eccf 100644 --- a/PermissionApp/src/foo/bar/permission/PermissionActivity.java +++ b/PermissionApp/src/foo/bar/permission/PermissionActivity.java @@ -19,7 +19,14 @@ package foo.bar.permission; import android.Manifest; import android.app.Activity; import android.app.LoaderManager; +import android.bluetooth.BluetoothDevice; +import android.companion.AssociationRequest; +import android.companion.BluetoothDeviceFilter; +import android.companion.BluetoothLEDeviceFilter; +import android.companion.CompanionDeviceManager; import android.content.CursorLoader; +import android.content.Intent; +import android.content.IntentSender; import android.content.Loader; import android.content.pm.PackageManager; import android.database.Cursor; @@ -44,6 +51,8 @@ public class PermissionActivity extends Activity implements LoaderManager.Loader public static final String LOG_TAG = "PermissionActivity"; + private static final int CHOOSE_DEVICE_REQUEST = 1; + private static final int CONTACTS_LOADER = 1; private static final int EVENTS_LOADER = 2; @@ -169,6 +178,13 @@ public class PermissionActivity extends Activity implements LoaderManager.Loader } @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == CHOOSE_DEVICE_REQUEST) { + BluetoothDevice device = data.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE); + } + } + + @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { switch (requestCode) { @@ -220,17 +236,20 @@ public class PermissionActivity extends Activity implements LoaderManager.Loader } private void showContacts() { - if (checkSelfPermission(Manifest.permission.READ_CONTACTS) - != PackageManager.PERMISSION_GRANTED) { - requestPermissions(new String[] {Manifest.permission.READ_CONTACTS}, - PERMISSIONS_REQUEST_READ_CONTACTS); - return; - } - - if (getLoaderManager().getLoader(CONTACTS_LOADER) == null) { - getLoaderManager().initLoader(CONTACTS_LOADER, null, this); - } - mListView.setAdapter(mContactsAdapter); + getPackageManager().isInstantApp(); + + +// if (checkSelfPermission(Manifest.permission.READ_CONTACTS) +// != PackageManager.PERMISSION_GRANTED) { +// requestPermissions(new String[] {Manifest.permission.READ_CONTACTS}, +// PERMISSIONS_REQUEST_READ_CONTACTS); +// return; +// } +// +// if (getLoaderManager().getLoader(CONTACTS_LOADER) == null) { +// getLoaderManager().initLoader(CONTACTS_LOADER, null, this); +// } +// mListView.setAdapter(mContactsAdapter); } private void showEvents() { |