Updates verify sample with latest APIs.
am: a06ec9916c
Change-Id: I47b060c55f2eb13821fb8e7fd50c36b800dbc2a4
diff --git a/content/documentsUi/DirectorySelection/Application/src/main/java/com/example/android/directoryselection/DirectorySelectionFragment.java b/content/documentsUi/DirectorySelection/Application/src/main/java/com/example/android/directoryselection/DirectorySelectionFragment.java
index 075f39b..a617e99 100644
--- a/content/documentsUi/DirectorySelection/Application/src/main/java/com/example/android/directoryselection/DirectorySelectionFragment.java
+++ b/content/documentsUi/DirectorySelection/Application/src/main/java/com/example/android/directoryselection/DirectorySelectionFragment.java
@@ -37,6 +37,7 @@
import android.widget.TextView;
import android.widget.Toast;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -198,8 +199,13 @@
ContentResolver contentResolver = getActivity().getContentResolver();
Uri docUri = DocumentsContract.buildDocumentUriUsingTree(uri,
DocumentsContract.getTreeDocumentId(uri));
- Uri directoryUri = DocumentsContract
- .createDocument(contentResolver, docUri, Document.MIME_TYPE_DIR, directoryName);
+ Uri directoryUri = null;
+ try {
+ directoryUri = DocumentsContract
+ .createDocument(contentResolver, docUri, Document.MIME_TYPE_DIR, directoryName);
+ } catch (IOException e) {
+ Log.w(TAG, "IOException", e);
+ }
if (directoryUri != null) {
Log.i(TAG, String.format(
"Created directory : %s, Document Uri : %s, Created directory Uri : %s",
diff --git a/content/documentsUi/DirectorySelection/gradle/wrapper/gradle-wrapper.properties b/content/documentsUi/DirectorySelection/gradle/wrapper/gradle-wrapper.properties
index 7714d09..d4585c3 100644
--- a/content/documentsUi/DirectorySelection/gradle/wrapper/gradle-wrapper.properties
+++ b/content/documentsUi/DirectorySelection/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
\ No newline at end of file
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
diff --git a/content/documentsUi/ScopedDirectoryAccess/Application/src/main/java/com/example/android/scopeddirectoryaccess/ScopedDirectoryAccessFragment.java b/content/documentsUi/ScopedDirectoryAccess/Application/src/main/java/com/example/android/scopeddirectoryaccess/ScopedDirectoryAccessFragment.java
index 23f1bb3..c19577c 100644
--- a/content/documentsUi/ScopedDirectoryAccess/Application/src/main/java/com/example/android/scopeddirectoryaccess/ScopedDirectoryAccessFragment.java
+++ b/content/documentsUi/ScopedDirectoryAccess/Application/src/main/java/com/example/android/scopeddirectoryaccess/ScopedDirectoryAccessFragment.java
@@ -22,6 +22,7 @@
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.storage.StorageManager;
@@ -30,6 +31,7 @@
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -38,6 +40,7 @@
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
+import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
@@ -47,6 +50,8 @@
*/
public class ScopedDirectoryAccessFragment extends Fragment {
+ private static final String TAG = "ScopedDirectorySample";
+
private static final String DIRECTORY_ENTRIES_KEY = "directory_entries";
private static final String SELECTED_DIRECTORY_KEY = "selected_directory";
private static final int OPEN_DIRECTORY_REQUEST_CODE = 1;
@@ -63,6 +68,9 @@
private TextView mNothingInDirectoryTextView;
private TextView mPrimaryVolumeNameTextView;
private Spinner mDirectoriesSpinner;
+ private LinearLayout mDirectoryAccessSettings;
+ private Button mLaunchDirectoryAccessSettings;
+
private DirectoryEntryAdapter mAdapter;
private ArrayList<DirectoryEntry> mDirectoryEntries;
@@ -102,12 +110,25 @@
public void onViewCreated(final View rootView, Bundle savedInstanceState) {
super.onViewCreated(rootView, savedInstanceState);
- mCurrentDirectoryTextView = (TextView) rootView
- .findViewById(R.id.textview_current_directory);
- mNothingInDirectoryTextView = (TextView) rootView
- .findViewById(R.id.textview_nothing_in_directory);
- mPrimaryVolumeNameTextView = (TextView) rootView
- .findViewById(R.id.textview_primary_volume_name);
+ mCurrentDirectoryTextView = rootView.findViewById(R.id.textview_current_directory);
+ mNothingInDirectoryTextView = rootView.findViewById(R.id.textview_nothing_in_directory);
+ mPrimaryVolumeNameTextView = rootView.findViewById(R.id.textview_primary_volume_name);
+
+ mDirectoryAccessSettings = rootView.findViewById(R.id.directory_access_settings);
+ mLaunchDirectoryAccessSettings = rootView.findViewById(R.id.launch);
+
+ // TODO: proper API check
+ if (Build.VERSION.CODENAME.equals("P")) {
+ mDirectoryAccessSettings.setVisibility(View.VISIBLE);
+ mLaunchDirectoryAccessSettings.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ // TODO: use API constant for intent name
+ Intent intent = new Intent("android.settings.DIRECTORY_ACCESS_SETTINGS");
+ startActivity(intent);
+ }
+ });
+ }
// Set onClickListener for the primary volume
Button openPictureButton = (Button) rootView
@@ -117,6 +138,12 @@
public void onClick(View view) {
String selected = mDirectoriesSpinner.getSelectedItem().toString();
String directoryName = getDirectoryName(selected);
+ Log.d(TAG, "Primary: selected=" + selected + " dir=" + directoryName);
+ if (directoryName == null) {
+ Toast.makeText(getContext(), "Cannot select ROOT on primary directory",
+ Toast.LENGTH_LONG).show();
+ return;
+ }
StorageVolume storageVolume = mStorageManager.getPrimaryStorageVolume();
Intent intent = storageVolume.createAccessIntent(directoryName);
startActivityForResult(intent, OPEN_DIRECTORY_REQUEST_CODE);
@@ -139,7 +166,7 @@
}
LinearLayout volumeArea = (LinearLayout) mActivity.getLayoutInflater()
.inflate(R.layout.volume_entry, containerVolumes);
- TextView volumeName = (TextView) volumeArea.findViewById(R.id.textview_volume_name);
+ final TextView volumeName = volumeArea.findViewById(R.id.textview_volume_name);
volumeName.setText(volumeDescription);
Button button = (Button) volumeArea.findViewById(R.id.button_open_directory);
button.setOnClickListener(new View.OnClickListener() {
@@ -147,6 +174,8 @@
public void onClick(View view) {
String selected = mDirectoriesSpinner.getSelectedItem().toString();
String directoryName = getDirectoryName(selected);
+ Log.d(TAG, "Secondary: volume=" + volumeName.getText() + ", selected=" + selected
+ + ", dir=" + directoryName);
Intent intent = volume.createAccessIntent(directoryName);
startActivityForResult(intent, OPEN_DIRECTORY_REQUEST_CODE);
}
@@ -221,6 +250,8 @@
private String getDirectoryName(String name) {
switch (name) {
+ case "ROOT":
+ return null;
case "ALARMS":
return Environment.DIRECTORY_ALARMS;
case "DCIM":
diff --git a/content/documentsUi/ScopedDirectoryAccess/Application/src/main/res/layout/fragment_scoped_directory_access.xml b/content/documentsUi/ScopedDirectoryAccess/Application/src/main/res/layout/fragment_scoped_directory_access.xml
index dfee2aa..b2c6d1a 100644
--- a/content/documentsUi/ScopedDirectoryAccess/Application/src/main/res/layout/fragment_scoped_directory_access.xml
+++ b/content/documentsUi/ScopedDirectoryAccess/Application/src/main/res/layout/fragment_scoped_directory_access.xml
@@ -51,6 +51,24 @@
</LinearLayout>
</LinearLayout>
+ <LinearLayout
+ android:id="@+id/directory_access_settings"
+ android:visibility="gone"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginBottom="@dimen/margin_small">
+ <TextView
+ android:text="@string/directory_access_settings"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <Button
+ android:id="@+id/launch"
+ android:text="@string/launch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
<Spinner
android:id="@+id/spinner_directories"
android:layout_width="wrap_content"
diff --git a/content/documentsUi/ScopedDirectoryAccess/Application/src/main/res/values/strings.xml b/content/documentsUi/ScopedDirectoryAccess/Application/src/main/res/values/strings.xml
index 8c4edcd..9798da9 100644
--- a/content/documentsUi/ScopedDirectoryAccess/Application/src/main/res/values/strings.xml
+++ b/content/documentsUi/ScopedDirectoryAccess/Application/src/main/res/values/strings.xml
@@ -20,7 +20,10 @@
<string name="selected_directory">Selected directory:\ </string>
<string name="nothing_in_directory">Nothing in the directory</string>
<string name="primary_volume_description">Internal shared storage</string>
+ <string name="launch">Launch</string>
+ <string name="directory_access_settings">Directory access settings</string>
<string-array name="directories">
+ <item>ROOT</item>
<item>ALARMS</item>
<item>DCIM</item>
<item>DOCUMENTS</item>
diff --git a/content/documentsUi/ScopedDirectoryAccess/gradle/wrapper/gradle-wrapper.properties b/content/documentsUi/ScopedDirectoryAccess/gradle/wrapper/gradle-wrapper.properties
index 361ce5a..6a5050a 100644
--- a/content/documentsUi/ScopedDirectoryAccess/gradle/wrapper/gradle-wrapper.properties
+++ b/content/documentsUi/ScopedDirectoryAccess/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
\ No newline at end of file
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
diff --git a/content/documentsUi/ScopedDirectoryAccess/template-params.xml b/content/documentsUi/ScopedDirectoryAccess/template-params.xml
index 4879b97..db7bcbc 100644
--- a/content/documentsUi/ScopedDirectoryAccess/template-params.xml
+++ b/content/documentsUi/ScopedDirectoryAccess/template-params.xml
@@ -22,12 +22,11 @@
<package>com.example.android.scopeddirectoryaccess</package>
<minSdk>24</minSdk>
- <compileSdkVersion>24</compileSdkVersion>
+ <compileSdkVersion>27</compileSdkVersion>
<targetSdkVersion>24</targetSdkVersion>
<!-- Include additional dependencies here.-->
- <dependency>com.android.support:recyclerview-v7:24.0.0</dependency>
- <dependency>com.android.support:support-v4:24.0.0</dependency>
+ <dependency>com.android.support:recyclerview-v7:27.0.2</dependency>
<template src="base" />
diff --git a/experimental/markgoldstein/snippets1.java b/experimental/markgoldstein/snippets1.java
new file mode 100644
index 0000000..90b2f46
--- /dev/null
+++ b/experimental/markgoldstein/snippets1.java
@@ -0,0 +1,28 @@
+// These are some test snippets
+//
+// [START snip1]
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.NotificationManagerCompat;
+import android.support.v4.app.NotificationCompat.WearableExtender;
+// [END snip1]
+
+
+// [START snip2]
+int notificationId = 001;
+The channel ID of the notification.
+String id = "my_channel_01";
+// Build intent for notification content
+Intent viewIntent = new Intent(this, ViewEventActivity.class);
+viewIntent.putExtra(EXTRA_EVENT_ID, eventId);
+PendingIntent viewPendingIntent =
+ PendingIntent.getActivity(this, 0, viewIntent, 0);
+
+ // Notification channel ID is ignored for Android 7.1.1
+ // (API level 25) and lower.
+ NotificationCompat.Builder notificationBuilder =
+ new NotificationCompat.Builder(this, id)
+ .setSmallIcon(R.drawable.ic_event)
+ .setContentTitle(eventTitle)
+ .setContentText(eventLocation)
+ .setContentIntent(viewPendingIntent);
+// [END snip2]
diff --git a/input/autofill/AutofillFramework/Application/build.gradle b/input/autofill/AutofillFramework/Application/build.gradle
index 4a877db..50bbdc3 100644
--- a/input/autofill/AutofillFramework/Application/build.gradle
+++ b/input/autofill/AutofillFramework/Application/build.gradle
@@ -7,12 +7,11 @@
'main'] // main sample code; look here for the interesting stuff.
android {
- compileSdkVersion 26
- buildToolsVersion '26.0.2'
+ compileSdkVersion "android-P"
defaultConfig {
minSdkVersion 26
- targetSdkVersion 26
+ targetSdkVersion 28
}
compileOptions {
@@ -38,11 +37,11 @@
}
dependencies {
- implementation "com.android.support:support-v4:26.1.0"
- implementation "com.android.support:support-v13:26.1.0"
- implementation "com.android.support:cardview-v7:26.1.0"
- implementation "com.android.support:appcompat-v7:26.1.0"
- implementation 'com.android.support:design:26.1.0'
+ implementation "com.android.support:support-v4:28.0.0-alpha1"
+ implementation "com.android.support:support-v13:28.0.0-alpha1"
+ implementation "com.android.support:cardview-v7:28.0.0-alpha1"
+ implementation "com.android.support:appcompat-v7:28.0.0-alpha1"
+ implementation 'com.android.support:design:28.0.0-alpha1'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
implementation group: 'com.google.guava', name: 'guava', version: '22.0-android'
}
diff --git a/input/autofill/AutofillFramework/afservice/build.gradle b/input/autofill/AutofillFramework/afservice/build.gradle
index 07ccafd..f614207 100644
--- a/input/autofill/AutofillFramework/afservice/build.gradle
+++ b/input/autofill/AutofillFramework/afservice/build.gradle
@@ -1,11 +1,11 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 27
+ compileSdkVersion "android-P"
defaultConfig {
minSdkVersion 26
- targetSdkVersion 27
+ targetSdkVersion 28
versionCode 1
versionName "1.0"
@@ -30,17 +30,17 @@
}
dependencies {
- implementation 'com.android.support:appcompat-v7:27.0.2'
+ implementation 'com.android.support:appcompat-v7:28.0.0-alpha1'
implementation "android.arch.persistence.room:runtime:1.0.0"
annotationProcessor 'android.arch.persistence.room:compiler:1.0.0'
implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation 'com.android.support:design:27.0.2'
+ implementation 'com.android.support:design:28.0.0-alpha1'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
- implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.1'
+ implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.2'
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation group: 'com.google.guava', name: 'guava', version: '22.0-android'
implementation "com.android.support.test.espresso:espresso-idling-resource:3.0.1"
- implementation "com.google.code.findbugs:jsr305:2.0.1"
+ implementation "com.google.code.findbugs:jsr305:3.0.2"
androidTestImplementation "junit:junit:4.12"
androidTestImplementation ("com.android.support.test.espresso:espresso-core:3.0.1")
diff --git a/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml b/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml
index f5d9a6d..b02a591 100644
--- a/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml
+++ b/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml
@@ -26,6 +26,25 @@
</intent-filter>
</service>
+ <service
+ android:name=".simple.BasicService"
+ android:label="Basic Autofill Service"
+ android:permission="android.permission.BIND_AUTOFILL_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.autofill.AutofillService" />
+ </intent-filter>
+ </service>
+
+ <service
+ android:name=".simple.BasicHeuristicsService"
+ android:label="Basic Heuristics Autofill Service"
+ android:permission="android.permission.BIND_AUTOFILL_SERVICE">
+
+ <intent-filter>
+ <action android:name="android.service.autofill.AutofillService" />
+ </intent-filter>
+ </service>
+
<activity
android:name=".AuthActivity"
android:taskAffinity=".AuthActivity"
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/MyAutofillService.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/MyAutofillService.java
index ace2cef..e9cdd6c 100644
--- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/MyAutofillService.java
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/MyAutofillService.java
@@ -147,15 +147,7 @@
private void fetchDataAndGenerateResponse(
HashMap<String, FieldTypeWithHeuristics> fieldTypesByAutofillHint, boolean responseAuth,
boolean datasetAuth, boolean manual, FillCallback callback) {
- if (manual) {
- IntentSender sender = ManualActivity.getManualIntentSenderForResponse(this);
- RemoteViews remoteViews = RemoteViewsHelper.viewsWithNoAuth(getPackageName(),
- getString(R.string.autofill_manual_prompt));
- FillResponse response = mResponseAdapter.buildManualResponse(sender, remoteViews);
- if (response != null) {
- callback.onSuccess(response);
- }
- } else if (responseAuth) {
+ if (responseAuth) {
// If the entire Autofill Response is authenticated, AuthActivity is used
// to generate Response.
IntentSender sender = AuthActivity.getAuthIntentSenderForResponse(this);
@@ -170,9 +162,22 @@
new DataCallback<List<DatasetWithFilledAutofillFields>>() {
@Override
public void onLoaded(List<DatasetWithFilledAutofillFields> datasets) {
- FillResponse response = mResponseAdapter.buildResponse(
- fieldTypesByAutofillHint, datasets, datasetAuth);
- callback.onSuccess(response);
+ if ((datasets == null || datasets.isEmpty()) && manual) {
+ IntentSender sender = ManualActivity
+ .getManualIntentSenderForResponse(MyAutofillService.this);
+ RemoteViews remoteViews = RemoteViewsHelper.viewsWithNoAuth(
+ getPackageName(),
+ getString(R.string.autofill_manual_prompt));
+ FillResponse response = mResponseAdapter.buildManualResponse(sender,
+ remoteViews);
+ if (response != null) {
+ callback.onSuccess(response);
+ }
+ } else {
+ FillResponse response = mResponseAdapter.buildResponse(
+ fieldTypesByAutofillHint, datasets, datasetAuth);
+ callback.onSuccess(response);
+ }
}
@Override
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicHeuristicsService.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicHeuristicsService.java
new file mode 100644
index 0000000..843440c
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicHeuristicsService.java
@@ -0,0 +1,105 @@
+/*
+ * 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.example.android.autofill.service.simple;
+
+import android.app.assist.AssistStructure.ViewNode;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+
+import com.example.android.autofill.service.MyAutofillService;
+
+/**
+ * A basic service that uses some rudimentary heuristics to identify fields that are not explicitly
+ * marked with autofill hints.
+ *
+ * <p>The goal of this class is to provide a simple autofill service implementation that is easy
+ * to understand and extend, but it should <strong>not</strong> be used as-is on real apps because
+ * it lacks fundamental security requirements such as data partitioning and package verification
+ * &mdashthese requirements are fullfilled by {@link MyAutofillService}. *
+ */
+public class BasicHeuristicsService extends BasicService {
+
+ private static final String TAG = "BasicHeuristicsService";
+
+ @Override
+ @Nullable
+ protected String getHint(@NonNull ViewNode node) {
+
+ // First try the explicit autofill hints...
+
+ String hint = super.getHint(node);
+ if (hint != null) return hint;
+
+ // Then try some rudimentary heuristics based on other node properties
+
+ String viewHint = node.getHint();
+ hint = inferHint(viewHint);
+ if (hint != null) {
+ Log.d(TAG, "Found hint using view hint(" + viewHint + "): " + hint);
+ return hint;
+ } else if (!TextUtils.isEmpty(viewHint)) {
+ Log.v(TAG, "No hint using view hint: " + viewHint);
+ }
+
+ String resourceId = node.getIdEntry();
+ hint = inferHint(resourceId);
+ if (hint != null) {
+ Log.d(TAG, "Found hint using resourceId(" + resourceId + "): " + hint);
+ return hint;
+ } else if (!TextUtils.isEmpty(resourceId)) {
+ Log.v(TAG, "No hint using resourceId: " + resourceId);
+ }
+
+ CharSequence text = node.getText();
+ CharSequence className = node.getClassName();
+ if (text != null && className != null && className.toString().contains("EditText")) {
+ hint = inferHint(text.toString());
+ if (hint != null) {
+ // NODE: text should not be logged, as it could contain PII
+ Log.d(TAG, "Found hint using text(" + text + "): " + hint);
+ return hint;
+ }
+ } else if (!TextUtils.isEmpty(text)) {
+ // NODE: text should not be logged, as it could contain PII
+ Log.v(TAG, "No hint using text: " + text + " and class " + className);
+ }
+ return null;
+ }
+
+ /**
+ * Uses heuristics to infer an autofill hint from a {@code string}.
+ *
+ * @return standard autofill hint, or {@code null} when it could not be inferred.
+ */
+ @Nullable
+ protected String inferHint(@Nullable String string) {
+ if (string == null) return null;
+
+ string = string.toLowerCase();
+ if (string.contains("password")) return View.AUTOFILL_HINT_PASSWORD;
+ if (string.contains("username")
+ || (string.contains("login") && string.contains("id")))
+ return View.AUTOFILL_HINT_USERNAME;
+ if (string.contains("email")) return View.AUTOFILL_HINT_EMAIL_ADDRESS;
+ if (string.contains("name")) return View.AUTOFILL_HINT_NAME;
+ if (string.contains("phone")) return View.AUTOFILL_HINT_PHONE;
+
+ return null;
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicService.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicService.java
new file mode 100644
index 0000000..92d6436
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicService.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.autofill.service.simple;
+
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.os.CancellationSignal;
+import android.service.autofill.AutofillService;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillContext;
+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.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.util.ArrayMap;
+import android.util.Log;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+import android.widget.Toast;
+
+import com.example.android.autofill.service.MyAutofillService;
+import com.example.android.autofill.service.R;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * A very basic {@link AutofillService} implementation that only shows dynamic-generated datasets
+ * and don't persist the saved data.
+ *
+ * <p>The goal of this class is to provide a simple autofill service implementation that is easy
+ * to understand and extend, but it should <strong>not</strong> be used as-is on real apps because
+ * it lacks fundamental security requirements such as data partitioning and package verification
+ * &mdashthese requirements are fullfilled by {@link MyAutofillService}.
+ */
+public class BasicService extends AutofillService {
+
+ private static final String TAG = "BasicService";
+
+ /**
+ * Number of datasets sent on each request - we're simple, that value is hardcoded in our DNA!
+ */
+ private static final int NUMBER_DATASETS = 4;
+
+ @Override
+ public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+ FillCallback callback) {
+ Log.d(TAG, "onFillRequest()");
+
+ // Find autofillable fields
+ AssistStructure structure = getLatestAssistStructure(request);
+ Map<String, AutofillId> fields = getAutofillableFields(structure);
+ Log.d(TAG, "autofillable fields:" + fields);
+
+ if (fields.isEmpty()) {
+ toast("No autofill hints found");
+ callback.onSuccess(null);
+ return;
+ }
+
+ // Create the base response
+ FillResponse.Builder response = new FillResponse.Builder();
+
+ // 1.Add the dynamic datasets
+ String packageName = getApplicationContext().getPackageName();
+ for (int i = 1; i <= NUMBER_DATASETS; i++) {
+ Dataset.Builder dataset = new Dataset.Builder();
+ for (Entry<String, AutofillId> field : fields.entrySet()) {
+ String hint = field.getKey();
+ AutofillId id = field.getValue();
+ String value = hint + i;
+ // We're simple - our dataset values are hardcoded as "hintN" (for example,
+ // "username1", "username2") and they're displayed as such, except if they're a
+ // password
+ String displayValue = hint.contains("password") ? "password for #" + i : value;
+ RemoteViews presentation = newDatasetPresentation(packageName, displayValue);
+ dataset.setValue(id, AutofillValue.forText(value), presentation);
+ }
+ response.addDataset(dataset.build());
+ }
+
+ // 2.Add save info
+ Collection<AutofillId> ids = fields.values();
+ AutofillId[] requiredIds = new AutofillId[ids.size()];
+ ids.toArray(requiredIds);
+ response.setSaveInfo(
+ // We're simple, so we're generic
+ new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC, requiredIds).build());
+
+ // 3.Profit!
+ callback.onSuccess(response.build());
+ }
+
+ @Override
+ public void onSaveRequest(SaveRequest request, SaveCallback callback) {
+ Log.d(TAG, "onSaveRequest()");
+ toast("Save not supported");
+ callback.onSuccess();
+ }
+
+ /**
+ * Parses the {@link AssistStructure} representing the activity being autofilled, and returns a
+ * map of autofillable fields (represented by their autofill ids) mapped by the hint associate
+ * with them.
+ *
+ * <p>An autofillable field is a {@link ViewNode} whose {@link #getHint(ViewNode)} metho
+ */
+ @NonNull
+ private Map<String, AutofillId> getAutofillableFields(@NonNull AssistStructure structure) {
+ Map<String, AutofillId> fields = new ArrayMap<>();
+ int nodes = structure.getWindowNodeCount();
+ for (int i = 0; i < nodes; i++) {
+ ViewNode node = structure.getWindowNodeAt(i).getRootViewNode();
+ addAutofillableFields(fields, node);
+ }
+ return fields;
+ }
+
+ /**
+ * Adds any autofillable view from the {@link ViewNode} and its descendants to the map.
+ */
+ private void addAutofillableFields(@NonNull Map<String, AutofillId> fields,
+ @NonNull ViewNode node) {
+ int type = node.getAutofillType();
+ // We're simple, we just autofill text fields.
+ if (type == View.AUTOFILL_TYPE_TEXT) {
+ String hint = getHint(node);
+ if (hint != null) {
+ AutofillId id = node.getAutofillId();
+ if (!fields.containsKey(hint)) {
+ Log.v(TAG, "Setting hint " + hint + " on " + id);
+ fields.put(hint, id);
+ } else {
+ Log.v(TAG, "Ignoring hint " + hint + " on " + id
+ + " because it was already set");
+ }
+ }
+ }
+ int childrenSize = node.getChildCount();
+ for (int i = 0; i < childrenSize; i++) {
+ addAutofillableFields(fields, node.getChildAt(i));
+ }
+ }
+
+ /**
+ * Gets the autofill hint associated with the given node.
+ *
+ * <p>By default it just return the first entry on the node's
+ * {@link ViewNode#getAutofillHints() autofillHints} (when available), but subclasses could
+ * extend it to use heuristics when the app developer didn't explicitly provide these hints.
+ *
+ */
+ @Nullable
+ protected String getHint(@NonNull ViewNode node) {
+ String[] hints = node.getAutofillHints();
+ if (hints == null) return null;
+
+ // We're simple, we only care about the first hint
+ String hint = hints[0].toLowerCase();
+ return hint;
+ }
+
+ /**
+ * Helper method to get the {@link AssistStructure} associated with the latest request
+ * in an autofill context.
+ */
+ @NonNull
+ private static AssistStructure getLatestAssistStructure(@NonNull FillRequest request) {
+ List<FillContext> fillContexts = request.getFillContexts();
+ return fillContexts.get(fillContexts.size() - 1).getStructure();
+ }
+
+ /**
+ * Helper method to create a dataset presentation with the given text.
+ */
+ @NonNull
+ private static RemoteViews newDatasetPresentation(@NonNull String packageName,
+ @NonNull CharSequence text) {
+ RemoteViews presentation =
+ new RemoteViews(packageName, R.layout.multidataset_service_list_item);
+ presentation.setTextViewText(R.id.text, text);
+ presentation.setImageViewResource(R.id.icon, R.mipmap.ic_launcher);
+ return presentation;
+ }
+
+ /**
+ * Displays a toast with the given message.
+ */
+ private void toast(@NonNull CharSequence message) {
+ Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
+ }
+}
diff --git a/input/autofill/AutofillFramework/kotlinApp/README.md b/input/autofill/AutofillFramework/kotlinApp/README.md
index 4b58f47..d0ab0e0 100644
--- a/input/autofill/AutofillFramework/kotlinApp/README.md
+++ b/input/autofill/AutofillFramework/kotlinApp/README.md
@@ -1,3 +1,5 @@
+Warning: This sample is currently outdated and you should reference the Java version instead.
+============================================================================================
Android AutofillFramework Sample (Kotlin)
=========================================
diff --git a/media/MediaBrowserService/Application/src/main/AndroidManifest.xml b/media/MediaBrowserService/Application/src/main/AndroidManifest.xml
index 6af29f0..c947564 100644
--- a/media/MediaBrowserService/Application/src/main/AndroidManifest.xml
+++ b/media/MediaBrowserService/Application/src/main/AndroidManifest.xml
@@ -18,6 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.mediasession">
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
diff --git a/security/FingerprintDialog/Application/src/main/Android.mk b/security/FingerprintDialog/Application/src/main/Android.mk
index 4bb3b0f..30420c0 100644
--- a/security/FingerprintDialog/Application/src/main/Android.mk
+++ b/security/FingerprintDialog/Application/src/main/Android.mk
@@ -21,6 +21,7 @@
LOCAL_MODULE_TAGS := samples
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := FingerprintDialog
+LOCAL_SDK_VERSION := current
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \
frameworks/support/v7/appcompat/res
LOCAL_AAPT_FLAGS := --auto-add-overlay \
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/layout/config_list_preview_and_complications_item.xml b/wearable/wear/WatchFace/Wearable/src/main/res/layout/config_list_preview_and_complications_item.xml
index dba01c9..41fb79a 100644
--- a/wearable/wear/WatchFace/Wearable/src/main/res/layout/config_list_preview_and_complications_item.xml
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/layout/config_list_preview_and_complications_item.xml
@@ -31,8 +31,7 @@
android:layout_height="@dimen/analog_complication_settings_preview_size"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
- android:background="@drawable/settings_watch_face_preview_background"
- android:importantForAccessibility="no"/>
+ android:background="@drawable/settings_watch_face_preview_background"/>
<View
android:id="@+id/watch_face_highlight"
@@ -40,8 +39,7 @@
android:layout_height="@dimen/analog_complication_settings_preview_size"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
- android:background="@drawable/settings_watch_face_preview_highlight"
- android:importantForAccessibility="no"/>
+ android:background="@drawable/settings_watch_face_preview_highlight"/>
<View
@@ -50,8 +48,7 @@
android:layout_height="@dimen/analog_complication_settings_preview_size"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
- android:background="@drawable/settings_watch_face_preview_arms_and_ticks"
- android:importantForAccessibility="no"/>
+ android:background="@drawable/settings_watch_face_preview_arms_and_ticks"/>
<ImageView
@@ -62,8 +59,7 @@
android:layout_centerVertical="true"
android:src="@drawable/added_complication"
style="?android:borderlessButtonStyle"
- android:background="@android:color/transparent"
- android:importantForAccessibility="no" />
+ android:background="@android:color/transparent"/>
<ImageButton
android:id="@+id/left_complication"
@@ -82,8 +78,7 @@
android:layout_centerVertical="true"
android:src="@drawable/added_complication"
style="?android:borderlessButtonStyle"
- android:background="@android:color/transparent"
- android:importantForAccessibility="no"/>
+ android:background="@android:color/transparent"/>
<ImageButton
android:id="@+id/right_complication"
@@ -93,4 +88,4 @@
android:layout_centerVertical="true"
style="?android:borderlessButtonStyle"
android:background="@android:color/transparent"/>
-</RelativeLayout>
+</RelativeLayout>
\ No newline at end of file
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/values/strings.xml b/wearable/wear/WatchFace/Wearable/src/main/res/values/strings.xml
index 8dbaccc..45f3186 100644
--- a/wearable/wear/WatchFace/Wearable/src/main/res/values/strings.xml
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/values/strings.xml
@@ -26,6 +26,7 @@
<string name="digital_am">AM</string>
<string name="digital_pm">PM</string>
+ <string name="analog_complication_config_name">Configuration</string>
<string name="complications_provider_incrementing_number">Incrementing Number</string>
<string name="analog_complication_preference_file_key">com.example.android.wearable.watchface.ANALOG_COMPLICATION_PREFERENCE_FILE_KEY</string>
@@ -47,8 +48,7 @@
<string name="color_navy">Navy</string>
<string name="color_red">Red</string>
<string name="color_white">White</string>
-
- <string name="analog_complication_config">Configure Watchface Complications</string>
+ <string name="analog_complication_config">Analog Complication Config</string>
<string name="add_complication">Add Complication</string>
<string name="edit_complication">Edit Complication <xliff:g id="name" example="World Clock">%1$s</xliff:g></string>
diff --git a/wearable/wear/WearAccessibilityApp/Wearable/src/main/res/layout/list_item_layout.xml b/wearable/wear/WearAccessibilityApp/Wearable/src/main/res/layout/list_item_layout.xml
index faff0b8..fe0d169 100644
--- a/wearable/wear/WearAccessibilityApp/Wearable/src/main/res/layout/list_item_layout.xml
+++ b/wearable/wear/WearAccessibilityApp/Wearable/src/main/res/layout/list_item_layout.xml
@@ -18,6 +18,8 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:minHeight="48dp"
+ android:gravity="center_vertical"
android:padding="4dp"
android:paddingStart="?android:listPreferredItemPaddingStart"
android:paddingEnd="?android:listPreferredItemPaddingEnd">
@@ -32,6 +34,5 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
- android:layout_centerVertical="true"
android:src="@drawable/arrow"/>
</RelativeLayout>
\ No newline at end of file