Follow sample code package-name convention for InlineFillService.
Bug: 155135358 Test: Manual. Install the application and dumpsys to check package name. Change-Id: Ifc338e40e6a2d37b775f6a60664231c2ce248eff
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.example.android.inlinefillservice;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.WindowManager;
|
||||
|
||||
/**
|
||||
* The activity which will be open when the inline suggestion is long pressed. It shows a dialog
|
||||
* that describes the source of the suggestion.
|
||||
*/
|
||||
public class AttributionDialogActivity extends Activity {
|
||||
static final String KEY_MSG = "AttributionDialogActivity:msg";
|
||||
static final String DEFAULT_MSG = "Hello";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
|
||||
Intent intent = getIntent();
|
||||
String msg = DEFAULT_MSG;
|
||||
if (intent != null) {
|
||||
msg = intent.getStringExtra(KEY_MSG);
|
||||
}
|
||||
Dialog dialog = createDialog(msg);
|
||||
dialog.setOnDismissListener(dialog1 -> finish());
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private Dialog createDialog(String msg) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder
|
||||
.setMessage("The suggestions are generated by the InlineFillService. " + msg)
|
||||
.setNegativeButton(
|
||||
"Settings",
|
||||
(dialog, id) -> {
|
||||
Intent intent = new Intent(this, SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
})
|
||||
.setPositiveButton(
|
||||
"Got it",
|
||||
(dialog, id) -> {
|
||||
// User cancelled the dialog
|
||||
});
|
||||
return builder.create();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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.example.android.inlinefillservice;
|
||||
|
||||
import static android.view.autofill.AutofillManager.EXTRA_AUTHENTICATION_RESULT;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.service.autofill.Dataset;
|
||||
import android.service.autofill.FillResponse;
|
||||
import android.util.ArrayMap;
|
||||
import android.view.autofill.AutofillId;
|
||||
import android.view.inputmethod.InlineSuggestionsRequest;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Activity used for autofill authentication, it simply sets the dataste upon tapping OK.
|
||||
*/
|
||||
// TODO(b/114236837): should display a small dialog, not take the full screen
|
||||
public class AuthActivity extends Activity {
|
||||
|
||||
private static final String EXTRA_DATASET = "dataset";
|
||||
private static final String EXTRA_HINTS = "hints";
|
||||
private static final String EXTRA_IDS = "ids";
|
||||
private static final String EXTRA_AUTH_DATASETS = "auth_datasets";
|
||||
private static final String EXTRA_INLINE_REQUEST = "inline_request";
|
||||
|
||||
private static int sPendingIntentId = 0;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.auth_activity);
|
||||
findViewById(R.id.yes).setOnClickListener((view) -> onYes());
|
||||
findViewById(R.id.no).setOnClickListener((view) -> onNo());
|
||||
}
|
||||
|
||||
private void onYes() {
|
||||
Intent myIntent = getIntent();
|
||||
Intent replyIntent = new Intent();
|
||||
Dataset dataset = myIntent.getParcelableExtra(EXTRA_DATASET);
|
||||
if (dataset != null) {
|
||||
replyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, dataset);
|
||||
} else {
|
||||
String[] hints = myIntent.getStringArrayExtra(EXTRA_HINTS);
|
||||
Parcelable[] ids = myIntent.getParcelableArrayExtra(EXTRA_IDS);
|
||||
boolean authenticateDatasets = myIntent.getBooleanExtra(EXTRA_AUTH_DATASETS, false);
|
||||
final InlineSuggestionsRequest inlineRequest =
|
||||
myIntent.getParcelableExtra(EXTRA_INLINE_REQUEST);
|
||||
int size = hints.length;
|
||||
ArrayMap<String, AutofillId> fields = new ArrayMap<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
fields.put(hints[i], (AutofillId) ids[i]);
|
||||
}
|
||||
FillResponse response =
|
||||
InlineFillService.createResponse(this, fields, 1, authenticateDatasets,
|
||||
Optional.ofNullable(inlineRequest));
|
||||
replyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, response);
|
||||
}
|
||||
setResult(RESULT_OK, replyIntent);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void onNo() {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
|
||||
public static IntentSender newIntentSenderForDataset(@NonNull Context context,
|
||||
@NonNull Dataset dataset) {
|
||||
return newIntentSender(context, dataset, null, null, false, null);
|
||||
}
|
||||
|
||||
public static IntentSender newIntentSenderForResponse(@NonNull Context context,
|
||||
@NonNull String[] hints, @NonNull AutofillId[] ids, boolean authenticateDatasets,
|
||||
@Nullable InlineSuggestionsRequest inlineRequest) {
|
||||
return newIntentSender(context, null, hints, ids, authenticateDatasets, inlineRequest);
|
||||
}
|
||||
|
||||
private static IntentSender newIntentSender(@NonNull Context context,
|
||||
@Nullable Dataset dataset, @Nullable String[] hints, @Nullable AutofillId[] ids,
|
||||
boolean authenticateDatasets, @Nullable InlineSuggestionsRequest inlineRequest) {
|
||||
Intent intent = new Intent(context, AuthActivity.class);
|
||||
if (dataset != null) {
|
||||
intent.putExtra(EXTRA_DATASET, dataset);
|
||||
} else {
|
||||
intent.putExtra(EXTRA_HINTS, hints);
|
||||
intent.putExtra(EXTRA_IDS, ids);
|
||||
intent.putExtra(EXTRA_AUTH_DATASETS, authenticateDatasets);
|
||||
intent.putExtra(EXTRA_INLINE_REQUEST, inlineRequest);
|
||||
}
|
||||
|
||||
return PendingIntent.getActivity(context, ++sPendingIntentId, intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
|
||||
}
|
||||
}
|
||||
@@ -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 com.example.android.inlinefillservice;
|
||||
|
||||
import static com.example.android.inlinefillservice.InlineFillService.TAG;
|
||||
|
||||
import android.app.assist.AssistStructure;
|
||||
import android.content.Context;
|
||||
import android.service.autofill.FillContext;
|
||||
import android.service.autofill.FillRequest;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.autofill.AutofillId;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
final class Helper {
|
||||
|
||||
/**
|
||||
* Displays a toast with the given message.
|
||||
*/
|
||||
static void showMessage(@NonNull Context context, @NonNull CharSequence message) {
|
||||
Log.i(TAG, message.toString());
|
||||
Toast.makeText(context, message, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the autofillable fields from the request through assist structure.
|
||||
*/
|
||||
static ArrayMap<String, AutofillId> getAutofillableFields(@NonNull FillRequest request) {
|
||||
AssistStructure structure = getLatestAssistStructure(request);
|
||||
return getAutofillableFields(structure);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 AssistStructure.ViewNode} whose getHint(ViewNode)
|
||||
* method.
|
||||
*/
|
||||
@NonNull
|
||||
private static ArrayMap<String, AutofillId> getAutofillableFields(
|
||||
@NonNull AssistStructure structure) {
|
||||
ArrayMap<String, AutofillId> fields = new ArrayMap<>();
|
||||
int nodes = structure.getWindowNodeCount();
|
||||
for (int i = 0; i < nodes; i++) {
|
||||
AssistStructure.ViewNode node = structure.getWindowNodeAt(i).getRootViewNode();
|
||||
addAutofillableFields(fields, node);
|
||||
}
|
||||
ArrayMap<String, AutofillId> result = new ArrayMap<>();
|
||||
int filedCount = fields.size();
|
||||
for (int i = 0; i < filedCount; i++) {
|
||||
String key = fields.keyAt(i);
|
||||
AutofillId value = fields.valueAt(i);
|
||||
// For fields with no hint we just use Field
|
||||
if (key.equals(value.toString())) {
|
||||
result.put("Field:" + i + "-", fields.valueAt(i));
|
||||
} else {
|
||||
result.put(key, fields.valueAt(i));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds any autofillable view from the {@link AssistStructure.ViewNode} and its descendants to
|
||||
* the map.
|
||||
*/
|
||||
private static void addAutofillableFields(@NonNull Map<String, AutofillId> fields,
|
||||
@NonNull AssistStructure.ViewNode node) {
|
||||
if (node.getAutofillType() == View.AUTOFILL_TYPE_TEXT) {
|
||||
if (!fields.containsValue(node.getAutofillId())) {
|
||||
final String key;
|
||||
if (node.getHint() != null) {
|
||||
key = node.getHint().toLowerCase();
|
||||
} else if (node.getAutofillHints() != null) {
|
||||
key = node.getAutofillHints()[0].toLowerCase();
|
||||
} else {
|
||||
key = node.getAutofillId().toString();
|
||||
}
|
||||
fields.put(key, node.getAutofillId());
|
||||
}
|
||||
}
|
||||
int childrenSize = node.getChildCount();
|
||||
for (int i = 0; i < childrenSize; i++) {
|
||||
addAutofillableFields(fields, node.getChildAt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 com.example.android.inlinefillservice;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.IntentSender;
|
||||
import android.os.CancellationSignal;
|
||||
import android.service.autofill.AutofillService;
|
||||
import android.service.autofill.FillCallback;
|
||||
import android.service.autofill.FillRequest;
|
||||
import android.service.autofill.FillResponse;
|
||||
import android.service.autofill.InlinePresentation;
|
||||
import android.service.autofill.SaveCallback;
|
||||
import android.service.autofill.SaveInfo;
|
||||
import android.service.autofill.SaveRequest;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.view.autofill.AutofillId;
|
||||
import android.view.inputmethod.InlineSuggestionsRequest;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A basic {@link AutofillService} implementation that only shows dynamic-generated datasets
|
||||
* and supports inline suggestions.
|
||||
*/
|
||||
public class InlineFillService extends AutofillService {
|
||||
|
||||
static final String TAG = "InlineFillService";
|
||||
|
||||
/**
|
||||
* Number of datasets sent on each request - we're simple, that value is hardcoded in our DNA!
|
||||
*/
|
||||
static final int NUMBER_DATASETS = 6;
|
||||
|
||||
private final boolean mAuthenticateResponses = false;
|
||||
private final boolean mAuthenticateDatasets = false;
|
||||
|
||||
@Override
|
||||
public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
|
||||
FillCallback callback) {
|
||||
Log.d(TAG, "onFillRequest()");
|
||||
|
||||
final Context context = getApplicationContext();
|
||||
|
||||
// Find autofillable fields
|
||||
ArrayMap<String, AutofillId> fields = Helper.getAutofillableFields(request);
|
||||
Log.d(TAG, "autofillable fields:" + fields);
|
||||
if (fields.isEmpty()) {
|
||||
Helper.showMessage(context,
|
||||
"InlineFillService could not figure out how to autofill this screen");
|
||||
callback.onSuccess(null);
|
||||
return;
|
||||
}
|
||||
final Optional<InlineSuggestionsRequest> inlineRequest =
|
||||
InlineRequestHelper.getInlineSuggestionsRequest(request);
|
||||
final int maxSuggestionsCount = InlineRequestHelper.getMaxSuggestionCount(inlineRequest,
|
||||
NUMBER_DATASETS);
|
||||
|
||||
// Create the base response
|
||||
final FillResponse response;
|
||||
if (mAuthenticateResponses) {
|
||||
int size = fields.size();
|
||||
String[] hints = new String[size];
|
||||
AutofillId[] ids = new AutofillId[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
hints[i] = fields.keyAt(i);
|
||||
ids[i] = fields.valueAt(i);
|
||||
}
|
||||
IntentSender authentication = AuthActivity.newIntentSenderForResponse(this, hints,
|
||||
ids, mAuthenticateDatasets, inlineRequest.orElse(null));
|
||||
RemoteViews presentation = ResponseHelper.newDatasetPresentation(getPackageName(),
|
||||
"Tap to auth response");
|
||||
|
||||
InlinePresentation inlinePresentation =
|
||||
InlineRequestHelper.maybeCreateInlineAuthenticationResponse(context,
|
||||
inlineRequest);
|
||||
response = new FillResponse.Builder()
|
||||
.setAuthentication(ids, authentication, presentation, inlinePresentation)
|
||||
.build();
|
||||
} else {
|
||||
response = createResponse(this, fields, maxSuggestionsCount, mAuthenticateDatasets,
|
||||
inlineRequest);
|
||||
}
|
||||
|
||||
callback.onSuccess(response);
|
||||
}
|
||||
|
||||
static FillResponse createResponse(@NonNull Context context,
|
||||
@NonNull ArrayMap<String, AutofillId> fields, int numDatasets,
|
||||
boolean authenticateDatasets,
|
||||
@NonNull Optional<InlineSuggestionsRequest> inlineRequest) {
|
||||
String packageName = context.getPackageName();
|
||||
FillResponse.Builder response = new FillResponse.Builder();
|
||||
// 1.Add the dynamic datasets
|
||||
for (int i = 0; i < numDatasets; i++) {
|
||||
if (authenticateDatasets) {
|
||||
response.addDataset(ResponseHelper.newLockedDataset(context, fields, packageName, i,
|
||||
inlineRequest));
|
||||
} else {
|
||||
response.addDataset(ResponseHelper.newUnlockedDataset(context, fields,
|
||||
packageName, i, inlineRequest));
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Add some inline actions
|
||||
if (inlineRequest.isPresent()) {
|
||||
response.addDataset(InlineRequestHelper.createInlineActionDataset(context, fields,
|
||||
inlineRequest.get(), R.drawable.ic_settings));
|
||||
response.addDataset(InlineRequestHelper.createInlineActionDataset(context, fields,
|
||||
inlineRequest.get(), R.drawable.ic_settings));
|
||||
}
|
||||
|
||||
// 3.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());
|
||||
|
||||
// 4.Profit!
|
||||
return response.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveRequest(SaveRequest request, SaveCallback callback) {
|
||||
Log.d(TAG, "onSaveRequest()");
|
||||
Helper.showMessage(getApplicationContext(), "InlineFillService doesn't support Save");
|
||||
callback.onSuccess();
|
||||
}
|
||||
}
|
||||
@@ -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.example.android.inlinefillservice;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.app.slice.Slice;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.service.autofill.Dataset;
|
||||
import android.service.autofill.FillRequest;
|
||||
import android.service.autofill.InlinePresentation;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.view.autofill.AutofillId;
|
||||
import android.view.inputmethod.InlineSuggestionsRequest;
|
||||
import android.widget.inline.InlinePresentationSpec;
|
||||
|
||||
import androidx.autofill.inline.v1.InlineSuggestionUi;
|
||||
import androidx.autofill.inline.v1.InlineSuggestionUi.Content;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class InlineRequestHelper {
|
||||
static Optional<InlineSuggestionsRequest> getInlineSuggestionsRequest(FillRequest request) {
|
||||
final InlineSuggestionsRequest inlineRequest = request.getInlineSuggestionsRequest();
|
||||
if (inlineRequest != null && inlineRequest.getMaxSuggestionCount() > 0
|
||||
&& !inlineRequest.getInlinePresentationSpecs().isEmpty()) {
|
||||
return Optional.of(inlineRequest);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
static int getMaxSuggestionCount(Optional<InlineSuggestionsRequest> inlineRequest, int max) {
|
||||
if (inlineRequest.isPresent()) {
|
||||
return Math.min(max, inlineRequest.get().getMaxSuggestionCount());
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
static InlinePresentation maybeCreateInlineAuthenticationResponse(
|
||||
Context context, Optional<InlineSuggestionsRequest> inlineRequest) {
|
||||
if (!inlineRequest.isPresent()) {
|
||||
return null;
|
||||
}
|
||||
final PendingIntent attribution = createAttribution(context,
|
||||
"Please tap on the chip to authenticate the Autofill response.");
|
||||
final Slice slice = createSlice("Tap to auth response", null, null, null, attribution);
|
||||
final InlinePresentationSpec spec = inlineRequest.get().getInlinePresentationSpecs().get(0);
|
||||
return new InlinePresentation(slice, spec, false);
|
||||
}
|
||||
|
||||
static InlinePresentation createInlineDataset(Context context,
|
||||
InlineSuggestionsRequest inlineRequest, String value, int index) {
|
||||
final PendingIntent attribution = createAttribution(context,
|
||||
"Please tap on the chip to autofill the value:" + value);
|
||||
final Slice slice = createSlice(value, null, null, null, attribution);
|
||||
index = Math.min(inlineRequest.getInlinePresentationSpecs().size() - 1, index);
|
||||
final InlinePresentationSpec spec = inlineRequest.getInlinePresentationSpecs().get(index);
|
||||
return new InlinePresentation(slice, spec, false);
|
||||
}
|
||||
|
||||
static Dataset createInlineActionDataset(Context context,
|
||||
ArrayMap<String, AutofillId> fields,
|
||||
InlineSuggestionsRequest inlineRequest, int drawable) {
|
||||
PendingIntent pendingIntent =
|
||||
PendingIntent.getActivity(context, 0, new Intent(context, SettingsActivity.class),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
Dataset.Builder builder =
|
||||
new Dataset.Builder()
|
||||
.setInlinePresentation(createInlineAction(context, inlineRequest, drawable))
|
||||
.setAuthentication(pendingIntent.getIntentSender());
|
||||
for (AutofillId fieldId : fields.values()) {
|
||||
builder.setValue(fieldId, null);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static InlinePresentation createInlineAction(Context context,
|
||||
InlineSuggestionsRequest inlineRequest, int drawable) {
|
||||
final PendingIntent attribution = createAttribution(context,
|
||||
"Please tap on the chip to launch the action.");
|
||||
final Icon icon = Icon.createWithResource(context, drawable);
|
||||
final Slice slice = createSlice(null, null, icon, null, attribution);
|
||||
// Reuse the first spec's height for the inline action size, as there isn't dedicated
|
||||
// value from the request for this.
|
||||
final InlinePresentationSpec spec = inlineRequest.getInlinePresentationSpecs().get(0);
|
||||
return new InlinePresentation(slice, spec, true);
|
||||
}
|
||||
|
||||
private static Slice createSlice(
|
||||
String title, String subtitle, Icon startIcon, Icon endIcon,
|
||||
PendingIntent attribution) {
|
||||
Content.Builder builder = InlineSuggestionUi.newContentBuilder(attribution);
|
||||
if (!TextUtils.isEmpty(title)) {
|
||||
builder.setTitle(title);
|
||||
}
|
||||
if (!TextUtils.isEmpty(subtitle)) {
|
||||
builder.setSubtitle(subtitle);
|
||||
}
|
||||
if (startIcon != null) {
|
||||
builder.setStartIcon(startIcon);
|
||||
}
|
||||
if (endIcon != null) {
|
||||
builder.setEndIcon(endIcon);
|
||||
}
|
||||
return builder.build().getSlice();
|
||||
}
|
||||
|
||||
private static PendingIntent createAttribution(Context context, String msg) {
|
||||
Intent intent = new Intent(context, AttributionDialogActivity.class);
|
||||
intent.putExtra(AttributionDialogActivity.KEY_MSG, msg);
|
||||
// Should use different request code to avoid the new intent overriding the old one.
|
||||
PendingIntent pendingIntent =
|
||||
PendingIntent.getActivity(
|
||||
context, msg.hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
return pendingIntent;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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.example.android.inlinefillservice;
|
||||
|
||||
import static com.example.android.inlinefillservice.InlineFillService.TAG;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.IntentSender;
|
||||
import android.service.autofill.Dataset;
|
||||
import android.service.autofill.InlinePresentation;
|
||||
import android.util.Log;
|
||||
import android.view.autofill.AutofillId;
|
||||
import android.view.autofill.AutofillValue;
|
||||
import android.view.inputmethod.InlineSuggestionsRequest;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
class ResponseHelper {
|
||||
|
||||
static Dataset newUnlockedDataset(@NonNull Context context,
|
||||
@NonNull Map<String, AutofillId> fields, @NonNull String packageName, int index,
|
||||
@NonNull Optional<InlineSuggestionsRequest> inlineRequest) {
|
||||
|
||||
Dataset.Builder dataset = new Dataset.Builder();
|
||||
for (Map.Entry<String, AutofillId> field : fields.entrySet()) {
|
||||
final String hint = field.getKey();
|
||||
final AutofillId id = field.getValue();
|
||||
final String value = hint + (index + 1);
|
||||
|
||||
// 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
|
||||
Log.d(TAG, "hint: " + hint);
|
||||
final String displayValue = hint.contains("password") ? "password for #" + (index + 1)
|
||||
: value;
|
||||
final RemoteViews presentation = newDatasetPresentation(packageName, displayValue);
|
||||
|
||||
// Add Inline Suggestion required info.
|
||||
if (inlineRequest.isPresent()) {
|
||||
Log.d(TAG, "Found InlineSuggestionsRequest in FillRequest: " + inlineRequest);
|
||||
final InlinePresentation inlinePresentation =
|
||||
InlineRequestHelper.createInlineDataset(context, inlineRequest.get(),
|
||||
displayValue, index);
|
||||
dataset.setValue(id, AutofillValue.forText(value), presentation,
|
||||
inlinePresentation);
|
||||
} else {
|
||||
dataset.setValue(id, AutofillValue.forText(value), presentation);
|
||||
}
|
||||
}
|
||||
|
||||
return dataset.build();
|
||||
}
|
||||
|
||||
static Dataset newLockedDataset(@NonNull Context context,
|
||||
@NonNull Map<String, AutofillId> fields, @NonNull String packageName, int index,
|
||||
@NonNull Optional<InlineSuggestionsRequest> inlineRequest) {
|
||||
Dataset unlockedDataset = ResponseHelper.newUnlockedDataset(context, fields,
|
||||
packageName, index, inlineRequest);
|
||||
|
||||
Dataset.Builder lockedDataset = new Dataset.Builder();
|
||||
for (Map.Entry<String, AutofillId> field : fields.entrySet()) {
|
||||
String hint = field.getKey();
|
||||
AutofillId id = field.getValue();
|
||||
String value = (index + 1) + "-" + hint;
|
||||
String displayValue = "Tap to auth " + value;
|
||||
IntentSender authentication =
|
||||
AuthActivity.newIntentSenderForDataset(context, unlockedDataset);
|
||||
RemoteViews presentation = newDatasetPresentation(packageName, displayValue);
|
||||
if (inlineRequest.isPresent()) {
|
||||
final InlinePresentation inlinePresentation =
|
||||
InlineRequestHelper.createInlineDataset(context, inlineRequest.get(),
|
||||
displayValue, index);
|
||||
lockedDataset.setValue(id, null, presentation, inlinePresentation)
|
||||
.setAuthentication(authentication);
|
||||
} else {
|
||||
lockedDataset.setValue(id, null, presentation)
|
||||
.setAuthentication(authentication);
|
||||
}
|
||||
}
|
||||
return lockedDataset.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create a dataset presentation with the givean text.
|
||||
*/
|
||||
@NonNull
|
||||
static RemoteViews newDatasetPresentation(@NonNull String packageName,
|
||||
@NonNull CharSequence text) {
|
||||
RemoteViews presentation =
|
||||
new RemoteViews(packageName, R.layout.list_item);
|
||||
presentation.setTextViewText(R.id.text, text);
|
||||
return presentation;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
package com.example.android.inlinefillservice;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
public class SettingsActivity extends Activity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.auth_activity);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user