mirror of
https://github.com/flutter/packages.git
synced 2025-06-30 14:47:22 +08:00
[quick_actions] Convert android to pigeon (#5099)
Converts from direct use of method channels to using Pigeon. Part of https://github.com/flutter/flutter/issues/117844 part of https://github.com/flutter/flutter/issues/117913
This commit is contained in:
@ -1,3 +1,7 @@
|
||||
## 1.0.9
|
||||
|
||||
* Changes method channels to pigeon.
|
||||
|
||||
## 1.0.8
|
||||
|
||||
* Adds pub topics to package metadata.
|
||||
|
@ -0,0 +1,322 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
// Autogenerated from Pigeon (v11.0.1), do not edit directly.
|
||||
// See also: https://pub.dev/packages/pigeon
|
||||
|
||||
package io.flutter.plugins.quickactions;
|
||||
|
||||
import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import io.flutter.plugin.common.BasicMessageChannel;
|
||||
import io.flutter.plugin.common.BinaryMessenger;
|
||||
import io.flutter.plugin.common.MessageCodec;
|
||||
import io.flutter.plugin.common.StandardMessageCodec;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** Generated class from Pigeon. */
|
||||
@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"})
|
||||
public class Messages {
|
||||
|
||||
/** Error class for passing custom error details to Flutter via a thrown PlatformException. */
|
||||
public static class FlutterError extends RuntimeException {
|
||||
|
||||
/** The error code. */
|
||||
public final String code;
|
||||
|
||||
/** The error details. Must be a datatype supported by the api codec. */
|
||||
public final Object details;
|
||||
|
||||
public FlutterError(@NonNull String code, @Nullable String message, @Nullable Object details) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.details = details;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected static ArrayList<Object> wrapError(@NonNull Throwable exception) {
|
||||
ArrayList<Object> errorList = new ArrayList<Object>(3);
|
||||
if (exception instanceof FlutterError) {
|
||||
FlutterError error = (FlutterError) exception;
|
||||
errorList.add(error.code);
|
||||
errorList.add(error.getMessage());
|
||||
errorList.add(error.details);
|
||||
} else {
|
||||
errorList.add(exception.toString());
|
||||
errorList.add(exception.getClass().getSimpleName());
|
||||
errorList.add(
|
||||
"Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception));
|
||||
}
|
||||
return errorList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Home screen quick-action shortcut item.
|
||||
*
|
||||
* <p>Generated class from Pigeon that represents data sent in messages.
|
||||
*/
|
||||
public static final class ShortcutItemMessage {
|
||||
/** The identifier of this item; should be unique within the app. */
|
||||
private @NonNull String type;
|
||||
|
||||
public @NonNull String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(@NonNull String setterArg) {
|
||||
if (setterArg == null) {
|
||||
throw new IllegalStateException("Nonnull field \"type\" is null.");
|
||||
}
|
||||
this.type = setterArg;
|
||||
}
|
||||
|
||||
/** Localized title of the item. */
|
||||
private @NonNull String localizedTitle;
|
||||
|
||||
public @NonNull String getLocalizedTitle() {
|
||||
return localizedTitle;
|
||||
}
|
||||
|
||||
public void setLocalizedTitle(@NonNull String setterArg) {
|
||||
if (setterArg == null) {
|
||||
throw new IllegalStateException("Nonnull field \"localizedTitle\" is null.");
|
||||
}
|
||||
this.localizedTitle = setterArg;
|
||||
}
|
||||
|
||||
/** Name of native resource to be displayed as the icon for this item. */
|
||||
private @Nullable String icon;
|
||||
|
||||
public @Nullable String getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public void setIcon(@Nullable String setterArg) {
|
||||
this.icon = setterArg;
|
||||
}
|
||||
|
||||
/** Constructor is non-public to enforce null safety; use Builder. */
|
||||
ShortcutItemMessage() {}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
private @Nullable String type;
|
||||
|
||||
public @NonNull Builder setType(@NonNull String setterArg) {
|
||||
this.type = setterArg;
|
||||
return this;
|
||||
}
|
||||
|
||||
private @Nullable String localizedTitle;
|
||||
|
||||
public @NonNull Builder setLocalizedTitle(@NonNull String setterArg) {
|
||||
this.localizedTitle = setterArg;
|
||||
return this;
|
||||
}
|
||||
|
||||
private @Nullable String icon;
|
||||
|
||||
public @NonNull Builder setIcon(@Nullable String setterArg) {
|
||||
this.icon = setterArg;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull ShortcutItemMessage build() {
|
||||
ShortcutItemMessage pigeonReturn = new ShortcutItemMessage();
|
||||
pigeonReturn.setType(type);
|
||||
pigeonReturn.setLocalizedTitle(localizedTitle);
|
||||
pigeonReturn.setIcon(icon);
|
||||
return pigeonReturn;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
ArrayList<Object> toList() {
|
||||
ArrayList<Object> toListResult = new ArrayList<Object>(3);
|
||||
toListResult.add(type);
|
||||
toListResult.add(localizedTitle);
|
||||
toListResult.add(icon);
|
||||
return toListResult;
|
||||
}
|
||||
|
||||
static @NonNull ShortcutItemMessage fromList(@NonNull ArrayList<Object> list) {
|
||||
ShortcutItemMessage pigeonResult = new ShortcutItemMessage();
|
||||
Object type = list.get(0);
|
||||
pigeonResult.setType((String) type);
|
||||
Object localizedTitle = list.get(1);
|
||||
pigeonResult.setLocalizedTitle((String) localizedTitle);
|
||||
Object icon = list.get(2);
|
||||
pigeonResult.setIcon((String) icon);
|
||||
return pigeonResult;
|
||||
}
|
||||
}
|
||||
|
||||
public interface Result<T> {
|
||||
@SuppressWarnings("UnknownNullness")
|
||||
void success(T result);
|
||||
|
||||
void error(@NonNull Throwable error);
|
||||
}
|
||||
|
||||
private static class AndroidQuickActionsApiCodec extends StandardMessageCodec {
|
||||
public static final AndroidQuickActionsApiCodec INSTANCE = new AndroidQuickActionsApiCodec();
|
||||
|
||||
private AndroidQuickActionsApiCodec() {}
|
||||
|
||||
@Override
|
||||
protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) {
|
||||
switch (type) {
|
||||
case (byte) 128:
|
||||
return ShortcutItemMessage.fromList((ArrayList<Object>) readValue(buffer));
|
||||
default:
|
||||
return super.readValueOfType(type, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) {
|
||||
if (value instanceof ShortcutItemMessage) {
|
||||
stream.write(128);
|
||||
writeValue(stream, ((ShortcutItemMessage) value).toList());
|
||||
} else {
|
||||
super.writeValue(stream, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||
public interface AndroidQuickActionsApi {
|
||||
/** Checks for, and returns the action that launched the app. */
|
||||
@Nullable
|
||||
String getLaunchAction();
|
||||
/** Sets the dynamic shortcuts for the app. */
|
||||
void setShortcutItems(
|
||||
@NonNull List<ShortcutItemMessage> itemsList, @NonNull Result<Void> result);
|
||||
/** Removes all dynamic shortcuts. */
|
||||
void clearShortcutItems();
|
||||
|
||||
/** The codec used by AndroidQuickActionsApi. */
|
||||
static @NonNull MessageCodec<Object> getCodec() {
|
||||
return AndroidQuickActionsApiCodec.INSTANCE;
|
||||
}
|
||||
/**
|
||||
* Sets up an instance of `AndroidQuickActionsApi` to handle messages through the
|
||||
* `binaryMessenger`.
|
||||
*/
|
||||
static void setup(
|
||||
@NonNull BinaryMessenger binaryMessenger, @Nullable AndroidQuickActionsApi api) {
|
||||
{
|
||||
BasicMessageChannel<Object> channel =
|
||||
new BasicMessageChannel<>(
|
||||
binaryMessenger,
|
||||
"dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsApi.getLaunchAction",
|
||||
getCodec());
|
||||
if (api != null) {
|
||||
channel.setMessageHandler(
|
||||
(message, reply) -> {
|
||||
ArrayList<Object> wrapped = new ArrayList<Object>();
|
||||
try {
|
||||
String output = api.getLaunchAction();
|
||||
wrapped.add(0, output);
|
||||
} catch (Throwable exception) {
|
||||
ArrayList<Object> wrappedError = wrapError(exception);
|
||||
wrapped = wrappedError;
|
||||
}
|
||||
reply.reply(wrapped);
|
||||
});
|
||||
} else {
|
||||
channel.setMessageHandler(null);
|
||||
}
|
||||
}
|
||||
{
|
||||
BasicMessageChannel<Object> channel =
|
||||
new BasicMessageChannel<>(
|
||||
binaryMessenger,
|
||||
"dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsApi.setShortcutItems",
|
||||
getCodec());
|
||||
if (api != null) {
|
||||
channel.setMessageHandler(
|
||||
(message, reply) -> {
|
||||
ArrayList<Object> wrapped = new ArrayList<Object>();
|
||||
ArrayList<Object> args = (ArrayList<Object>) message;
|
||||
List<ShortcutItemMessage> itemsListArg = (List<ShortcutItemMessage>) args.get(0);
|
||||
Result<Void> resultCallback =
|
||||
new Result<Void>() {
|
||||
public void success(Void result) {
|
||||
wrapped.add(0, null);
|
||||
reply.reply(wrapped);
|
||||
}
|
||||
|
||||
public void error(Throwable error) {
|
||||
ArrayList<Object> wrappedError = wrapError(error);
|
||||
reply.reply(wrappedError);
|
||||
}
|
||||
};
|
||||
|
||||
api.setShortcutItems(itemsListArg, resultCallback);
|
||||
});
|
||||
} else {
|
||||
channel.setMessageHandler(null);
|
||||
}
|
||||
}
|
||||
{
|
||||
BasicMessageChannel<Object> channel =
|
||||
new BasicMessageChannel<>(
|
||||
binaryMessenger,
|
||||
"dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsApi.clearShortcutItems",
|
||||
getCodec());
|
||||
if (api != null) {
|
||||
channel.setMessageHandler(
|
||||
(message, reply) -> {
|
||||
ArrayList<Object> wrapped = new ArrayList<Object>();
|
||||
try {
|
||||
api.clearShortcutItems();
|
||||
wrapped.add(0, null);
|
||||
} catch (Throwable exception) {
|
||||
ArrayList<Object> wrappedError = wrapError(exception);
|
||||
wrapped = wrappedError;
|
||||
}
|
||||
reply.reply(wrapped);
|
||||
});
|
||||
} else {
|
||||
channel.setMessageHandler(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Generated class from Pigeon that represents Flutter messages that can be called from Java. */
|
||||
public static class AndroidQuickActionsFlutterApi {
|
||||
private final @NonNull BinaryMessenger binaryMessenger;
|
||||
|
||||
public AndroidQuickActionsFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) {
|
||||
this.binaryMessenger = argBinaryMessenger;
|
||||
}
|
||||
|
||||
/** Public interface for sending reply. */
|
||||
@SuppressWarnings("UnknownNullness")
|
||||
public interface Reply<T> {
|
||||
void reply(T reply);
|
||||
}
|
||||
/** The codec used by AndroidQuickActionsFlutterApi. */
|
||||
static @NonNull MessageCodec<Object> getCodec() {
|
||||
return new StandardMessageCodec();
|
||||
}
|
||||
/** Sends a string representing a shortcut from the native platform to the app. */
|
||||
public void launchAction(@NonNull String actionArg, @NonNull Reply<Void> callback) {
|
||||
BasicMessageChannel<Object> channel =
|
||||
new BasicMessageChannel<>(
|
||||
binaryMessenger,
|
||||
"dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsFlutterApi.launchAction",
|
||||
getCodec());
|
||||
channel.send(
|
||||
new ArrayList<Object>(Collections.singletonList(actionArg)),
|
||||
channelReply -> callback.reply(null));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,180 +0,0 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.quickactions;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.pm.ShortcutManager;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import androidx.annotation.NonNull;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler {
|
||||
protected static final String EXTRA_ACTION = "some unique action key";
|
||||
|
||||
private final Context context;
|
||||
private Activity activity;
|
||||
|
||||
MethodCallHandlerImpl(Context context, Activity activity) {
|
||||
this.context = context;
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
void setActivity(Activity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
|
||||
// We already know that this functionality does not work for anything
|
||||
// lower than API 25 so we chose not to return error. Instead we do nothing.
|
||||
result.success(null);
|
||||
return;
|
||||
}
|
||||
ShortcutManager shortcutManager =
|
||||
(ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE);
|
||||
switch (call.method) {
|
||||
case "setShortcutItems":
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) {
|
||||
List<Map<String, String>> serializedShortcuts = Objects.requireNonNull(call.arguments());
|
||||
List<ShortcutInfo> shortcuts = deserializeShortcuts(serializedShortcuts);
|
||||
|
||||
Executor uiThreadExecutor = new UiThreadExecutor();
|
||||
ThreadPoolExecutor executor =
|
||||
new ThreadPoolExecutor(0, 1, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
|
||||
|
||||
executor.execute(
|
||||
() -> {
|
||||
boolean dynamicShortcutsSet = false;
|
||||
try {
|
||||
shortcutManager.setDynamicShortcuts(shortcuts);
|
||||
dynamicShortcutsSet = true;
|
||||
} catch (Exception e) {
|
||||
// Leave dynamicShortcutsSet as false
|
||||
}
|
||||
|
||||
final boolean didSucceed = dynamicShortcutsSet;
|
||||
|
||||
// TODO(camsim99): Move re-dispatch below to background thread when Flutter 2.8+ is
|
||||
// stable.
|
||||
uiThreadExecutor.execute(
|
||||
() -> {
|
||||
if (didSucceed) {
|
||||
result.success(null);
|
||||
} else {
|
||||
result.error(
|
||||
"quick_action_setshortcutitems_failure",
|
||||
"Exception thrown when setting dynamic shortcuts",
|
||||
null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return;
|
||||
case "clearShortcutItems":
|
||||
shortcutManager.removeAllDynamicShortcuts();
|
||||
break;
|
||||
case "getLaunchAction":
|
||||
if (activity == null) {
|
||||
result.error(
|
||||
"quick_action_getlaunchaction_no_activity",
|
||||
"There is no activity available when launching action",
|
||||
null);
|
||||
return;
|
||||
}
|
||||
final Intent intent = activity.getIntent();
|
||||
final String launchAction = intent.getStringExtra(EXTRA_ACTION);
|
||||
if (launchAction != null && !launchAction.isEmpty()) {
|
||||
shortcutManager.reportShortcutUsed(launchAction);
|
||||
intent.removeExtra(EXTRA_ACTION);
|
||||
}
|
||||
result.success(launchAction);
|
||||
return;
|
||||
default:
|
||||
result.notImplemented();
|
||||
return;
|
||||
}
|
||||
result.success(null);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
private List<ShortcutInfo> deserializeShortcuts(List<Map<String, String>> shortcuts) {
|
||||
final List<ShortcutInfo> shortcutInfos = new ArrayList<>();
|
||||
|
||||
for (Map<String, String> shortcut : shortcuts) {
|
||||
final String icon = shortcut.get("icon");
|
||||
final String type = shortcut.get("type");
|
||||
final String title = shortcut.get("localizedTitle");
|
||||
final ShortcutInfo.Builder shortcutBuilder = new ShortcutInfo.Builder(context, type);
|
||||
|
||||
final int resourceId = loadResourceId(context, icon);
|
||||
final Intent intent = getIntentToOpenMainActivity(type);
|
||||
|
||||
if (resourceId > 0) {
|
||||
shortcutBuilder.setIcon(Icon.createWithResource(context, resourceId));
|
||||
}
|
||||
|
||||
final ShortcutInfo shortcutInfo =
|
||||
shortcutBuilder.setLongLabel(title).setShortLabel(title).setIntent(intent).build();
|
||||
shortcutInfos.add(shortcutInfo);
|
||||
}
|
||||
return shortcutInfos;
|
||||
}
|
||||
|
||||
// This method requires doing dynamic resource lookup, which is a discouraged API.
|
||||
@SuppressWarnings("DiscouragedApi")
|
||||
private int loadResourceId(Context context, String icon) {
|
||||
if (icon == null) {
|
||||
return 0;
|
||||
}
|
||||
final String packageName = context.getPackageName();
|
||||
final Resources res = context.getResources();
|
||||
final int resourceId = res.getIdentifier(icon, "drawable", packageName);
|
||||
|
||||
if (resourceId == 0) {
|
||||
return res.getIdentifier(icon, "mipmap", packageName);
|
||||
} else {
|
||||
return resourceId;
|
||||
}
|
||||
}
|
||||
|
||||
private Intent getIntentToOpenMainActivity(String type) {
|
||||
final String packageName = context.getPackageName();
|
||||
|
||||
return context
|
||||
.getPackageManager()
|
||||
.getLaunchIntentForPackage(packageName)
|
||||
.setAction(Intent.ACTION_RUN)
|
||||
.putExtra(EXTRA_ACTION, type)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
}
|
||||
|
||||
static class UiThreadExecutor implements Executor {
|
||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
handler.post(command);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,193 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.quickactions;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.pm.ShortcutManager;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import androidx.annotation.ChecksSdkIntAtLeast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import io.flutter.plugins.quickactions.Messages.AndroidQuickActionsApi;
|
||||
import io.flutter.plugins.quickactions.Messages.FlutterError;
|
||||
import io.flutter.plugins.quickactions.Messages.Result;
|
||||
import io.flutter.plugins.quickactions.Messages.ShortcutItemMessage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
final class QuickActions implements AndroidQuickActionsApi {
|
||||
protected static final String EXTRA_ACTION = "some unique action key";
|
||||
|
||||
private final Context context;
|
||||
private Activity activity;
|
||||
|
||||
QuickActions(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
void setActivity(Activity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
public Activity getActivity() {
|
||||
return this.activity;
|
||||
}
|
||||
|
||||
// Returns true when running on a version of Android that supports quick actions.
|
||||
// When this returns false, methods should silently no-op, per the documented behavior (see README.md).
|
||||
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.N_MR1)
|
||||
boolean isVersionAllowed() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShortcutItems(
|
||||
@NonNull List<ShortcutItemMessage> itemsList, @NonNull Result<Void> result) {
|
||||
if (!isVersionAllowed()) {
|
||||
result.success(null);
|
||||
return;
|
||||
}
|
||||
ShortcutManager shortcutManager =
|
||||
(ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE);
|
||||
List<ShortcutInfo> shortcuts = shortcutItemMessageToShortcutInfo(itemsList);
|
||||
Executor uiThreadExecutor = new UiThreadExecutor();
|
||||
ThreadPoolExecutor executor =
|
||||
new ThreadPoolExecutor(0, 1, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
|
||||
|
||||
executor.execute(
|
||||
() -> {
|
||||
boolean dynamicShortcutsSet = false;
|
||||
try {
|
||||
shortcutManager.setDynamicShortcuts(shortcuts);
|
||||
dynamicShortcutsSet = true;
|
||||
} catch (Exception e) {
|
||||
// Leave dynamicShortcutsSet as false
|
||||
}
|
||||
|
||||
final boolean didSucceed = dynamicShortcutsSet;
|
||||
|
||||
// TODO(camsim99): Investigate removing all of the executor logic in favor of background channels.
|
||||
uiThreadExecutor.execute(
|
||||
() -> {
|
||||
if (didSucceed) {
|
||||
result.success(null);
|
||||
} else {
|
||||
result.error(
|
||||
new FlutterError(
|
||||
"quick_action_setshortcutitems_failure",
|
||||
"Exception thrown when setting dynamic shortcuts",
|
||||
null));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearShortcutItems() {
|
||||
if (!isVersionAllowed()) {
|
||||
return;
|
||||
}
|
||||
ShortcutManager shortcutManager =
|
||||
(ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE);
|
||||
shortcutManager.removeAllDynamicShortcuts();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getLaunchAction() {
|
||||
if (!isVersionAllowed()) {
|
||||
return null;
|
||||
}
|
||||
ShortcutManager shortcutManager =
|
||||
(ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE);
|
||||
if (activity == null) {
|
||||
throw new FlutterError(
|
||||
"quick_action_getlaunchaction_no_activity",
|
||||
"There is no activity available when launching action",
|
||||
null);
|
||||
}
|
||||
final Intent intent = activity.getIntent();
|
||||
final String launchAction = intent.getStringExtra(EXTRA_ACTION);
|
||||
if (launchAction != null && !launchAction.isEmpty()) {
|
||||
shortcutManager.reportShortcutUsed(launchAction);
|
||||
intent.removeExtra(EXTRA_ACTION);
|
||||
}
|
||||
return launchAction;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
private List<ShortcutInfo> shortcutItemMessageToShortcutInfo(
|
||||
@NonNull List<ShortcutItemMessage> shortcuts) {
|
||||
final List<ShortcutInfo> shortcutInfos = new ArrayList<>();
|
||||
|
||||
for (ShortcutItemMessage shortcut : shortcuts) {
|
||||
final String icon = shortcut.getIcon();
|
||||
final String type = shortcut.getType();
|
||||
final String title = shortcut.getLocalizedTitle();
|
||||
final ShortcutInfo.Builder shortcutBuilder = new ShortcutInfo.Builder(context, type);
|
||||
|
||||
final int resourceId = loadResourceId(context, icon);
|
||||
final Intent intent = getIntentToOpenMainActivity(type);
|
||||
|
||||
if (resourceId > 0) {
|
||||
shortcutBuilder.setIcon(Icon.createWithResource(context, resourceId));
|
||||
}
|
||||
|
||||
final ShortcutInfo shortcutInfo =
|
||||
shortcutBuilder.setLongLabel(title).setShortLabel(title).setIntent(intent).build();
|
||||
shortcutInfos.add(shortcutInfo);
|
||||
}
|
||||
return shortcutInfos;
|
||||
}
|
||||
|
||||
// This method requires doing dynamic resource lookup, which is a discouraged API.
|
||||
@SuppressWarnings("DiscouragedApi")
|
||||
private int loadResourceId(Context context, String icon) {
|
||||
if (icon == null) {
|
||||
return 0;
|
||||
}
|
||||
final String packageName = context.getPackageName();
|
||||
final Resources res = context.getResources();
|
||||
final int resourceId = res.getIdentifier(icon, "drawable", packageName);
|
||||
|
||||
if (resourceId == 0) {
|
||||
return res.getIdentifier(icon, "mipmap", packageName);
|
||||
} else {
|
||||
return resourceId;
|
||||
}
|
||||
}
|
||||
|
||||
private Intent getIntentToOpenMainActivity(String type) {
|
||||
final String packageName = context.getPackageName();
|
||||
|
||||
return context
|
||||
.getPackageManager()
|
||||
.getLaunchIntentForPackage(packageName)
|
||||
.setAction(Intent.ACTION_RUN)
|
||||
.putExtra(EXTRA_ACTION, type)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
}
|
||||
|
||||
static class UiThreadExecutor implements Executor {
|
||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
handler.post(command);
|
||||
}
|
||||
}
|
||||
}
|
@ -9,23 +9,22 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ShortcutManager;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.ChecksSdkIntAtLeast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin;
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
|
||||
import io.flutter.plugin.common.BinaryMessenger;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
import io.flutter.plugin.common.PluginRegistry.NewIntentListener;
|
||||
import io.flutter.plugins.quickactions.Messages.AndroidQuickActionsFlutterApi;
|
||||
|
||||
/** QuickActionsPlugin */
|
||||
public class QuickActionsPlugin implements FlutterPlugin, ActivityAware, NewIntentListener {
|
||||
private static final String CHANNEL_ID = "plugins.flutter.io/quick_actions_android";
|
||||
private static final String TAG = "QuickActionsAndroid";
|
||||
|
||||
private MethodChannel channel;
|
||||
private MethodCallHandlerImpl handler;
|
||||
private Activity activity;
|
||||
private QuickActions quickActions;
|
||||
private AndroidQuickActionsFlutterApi quickActionsFlutterApi;
|
||||
private final @NonNull AndroidSdkChecker sdkChecker;
|
||||
|
||||
// Interface for an injectable SDK version checker.
|
||||
@ -52,31 +51,40 @@ public class QuickActionsPlugin implements FlutterPlugin, ActivityAware, NewInte
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void registerWith(
|
||||
@NonNull io.flutter.plugin.common.PluginRegistry.Registrar registrar) {
|
||||
final QuickActionsPlugin plugin = new QuickActionsPlugin();
|
||||
plugin.setupChannel(registrar.messenger(), registrar.context(), registrar.activity());
|
||||
QuickActions quickActions = new QuickActions(registrar.context());
|
||||
quickActions.setActivity(registrar.activity());
|
||||
Messages.AndroidQuickActionsApi.setup(registrar.messenger(), quickActions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
|
||||
setupChannel(binding.getBinaryMessenger(), binding.getApplicationContext(), null);
|
||||
this.quickActions = new QuickActions(binding.getApplicationContext());
|
||||
Messages.AndroidQuickActionsApi.setup(binding.getBinaryMessenger(), quickActions);
|
||||
this.quickActionsFlutterApi = new AndroidQuickActionsFlutterApi(binding.getBinaryMessenger());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
|
||||
teardownChannel();
|
||||
Messages.AndroidQuickActionsApi.setup(binding.getBinaryMessenger(), null);
|
||||
this.quickActions = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
|
||||
activity = binding.getActivity();
|
||||
handler.setActivity(activity);
|
||||
if (this.quickActions == null) {
|
||||
Log.wtf(TAG, "quickActions was never set.");
|
||||
return;
|
||||
}
|
||||
|
||||
Activity activity = binding.getActivity();
|
||||
this.quickActions.setActivity(activity);
|
||||
binding.addOnNewIntentListener(this);
|
||||
onNewIntent(activity.getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromActivity() {
|
||||
handler.setActivity(null);
|
||||
quickActions.setActivity(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -96,27 +104,20 @@ public class QuickActionsPlugin implements FlutterPlugin, ActivityAware, NewInte
|
||||
if (!sdkChecker.sdkIsAtLeast(Build.VERSION_CODES.N_MR1)) {
|
||||
return false;
|
||||
}
|
||||
Activity activity = this.quickActions.getActivity();
|
||||
// Notify the Dart side if the launch intent has the intent extra relevant to quick actions.
|
||||
if (intent.hasExtra(MethodCallHandlerImpl.EXTRA_ACTION) && channel != null) {
|
||||
if (intent.hasExtra(QuickActions.EXTRA_ACTION) && activity != null) {
|
||||
Context context = activity.getApplicationContext();
|
||||
ShortcutManager shortcutManager =
|
||||
(ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE);
|
||||
String shortcutId = intent.getStringExtra(MethodCallHandlerImpl.EXTRA_ACTION);
|
||||
channel.invokeMethod("launch", shortcutId);
|
||||
String shortcutId = intent.getStringExtra(QuickActions.EXTRA_ACTION);
|
||||
quickActionsFlutterApi.launchAction(
|
||||
shortcutId,
|
||||
value -> {
|
||||
// noop
|
||||
});
|
||||
shortcutManager.reportShortcutUsed(shortcutId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void setupChannel(BinaryMessenger messenger, Context context, Activity activity) {
|
||||
channel = new MethodChannel(messenger, CHANNEL_ID);
|
||||
handler = new MethodCallHandlerImpl(context, activity);
|
||||
channel.setMethodCallHandler(handler);
|
||||
}
|
||||
|
||||
private void teardownChannel() {
|
||||
channel.setMethodCallHandler(null);
|
||||
channel = null;
|
||||
handler = null;
|
||||
}
|
||||
}
|
||||
|
@ -4,11 +4,9 @@
|
||||
|
||||
package io.flutter.plugins.quickactions;
|
||||
|
||||
import static io.flutter.plugins.quickactions.MethodCallHandlerImpl.EXTRA_ACTION;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static io.flutter.plugins.quickactions.QuickActions.EXTRA_ACTION;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ -21,15 +19,12 @@ import androidx.annotation.Nullable;
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding;
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
|
||||
import io.flutter.plugin.common.BinaryMessenger;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.StandardMethodCodec;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.ByteBuffer;
|
||||
import org.junit.Test;
|
||||
|
||||
public class QuickActionsTest {
|
||||
private static class TestBinaryMessenger implements BinaryMessenger {
|
||||
public MethodCall lastMethodCall;
|
||||
public boolean launchActionCalled;
|
||||
|
||||
@Override
|
||||
public void send(@NonNull String channel, @Nullable ByteBuffer message) {
|
||||
@ -41,9 +36,8 @@ public class QuickActionsTest {
|
||||
@NonNull String channel,
|
||||
@Nullable ByteBuffer message,
|
||||
@Nullable final BinaryReply callback) {
|
||||
if (channel.equals("plugins.flutter.io/quick_actions_android")) {
|
||||
lastMethodCall =
|
||||
StandardMethodCodec.INSTANCE.decodeMethodCall((ByteBuffer) message.position(0));
|
||||
if (channel.contains("launchAction")) {
|
||||
launchActionCalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,9 +69,6 @@ public class QuickActionsTest {
|
||||
final QuickActionsPlugin plugin =
|
||||
new QuickActionsPlugin((version) -> SUPPORTED_BUILD >= version);
|
||||
setUpMessengerAndFlutterPluginBinding(testBinaryMessenger, plugin);
|
||||
Field handler = plugin.getClass().getDeclaredField("handler");
|
||||
handler.setAccessible(true);
|
||||
handler.set(plugin, mock(MethodCallHandlerImpl.class));
|
||||
final Intent mockIntent = createMockIntentWithQuickActionExtra();
|
||||
final Activity mockMainActivity = mock(Activity.class);
|
||||
when(mockMainActivity.getIntent()).thenReturn(mockIntent);
|
||||
@ -93,9 +84,7 @@ public class QuickActionsTest {
|
||||
plugin.onAttachedToActivity(mockActivityPluginBinding);
|
||||
|
||||
// Assert
|
||||
assertNotNull(testBinaryMessenger.lastMethodCall);
|
||||
assertEquals(testBinaryMessenger.lastMethodCall.method, "launch");
|
||||
assertEquals(testBinaryMessenger.lastMethodCall.arguments, SHORTCUT_TYPE);
|
||||
assertTrue(testBinaryMessenger.launchActionCalled);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -111,7 +100,7 @@ public class QuickActionsTest {
|
||||
final boolean onNewIntentReturn = plugin.onNewIntent(mockIntent);
|
||||
|
||||
// Assert
|
||||
assertNull(testBinaryMessenger.lastMethodCall);
|
||||
assertFalse(testBinaryMessenger.launchActionCalled);
|
||||
assertFalse(onNewIntentReturn);
|
||||
}
|
||||
|
||||
@ -137,9 +126,7 @@ public class QuickActionsTest {
|
||||
final boolean onNewIntentReturn = plugin.onNewIntent(mockIntent);
|
||||
|
||||
// Assert
|
||||
assertNotNull(testBinaryMessenger.lastMethodCall);
|
||||
assertEquals(testBinaryMessenger.lastMethodCall.method, "launch");
|
||||
assertEquals(testBinaryMessenger.lastMethodCall.arguments, SHORTCUT_TYPE);
|
||||
assertTrue(testBinaryMessenger.launchActionCalled);
|
||||
assertFalse(onNewIntentReturn);
|
||||
}
|
||||
|
||||
|
@ -3,54 +3,62 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:quick_actions_platform_interface/quick_actions_platform_interface.dart';
|
||||
|
||||
import 'src/messages.g.dart';
|
||||
|
||||
export 'package:quick_actions_platform_interface/types/types.dart';
|
||||
|
||||
const MethodChannel _channel =
|
||||
MethodChannel('plugins.flutter.io/quick_actions_android');
|
||||
late QuickActionHandler _handler;
|
||||
|
||||
/// An implementation of [QuickActionsPlatform] that for Android.
|
||||
/// An implementation of [QuickActionsPlatform] for Android.
|
||||
class QuickActionsAndroid extends QuickActionsPlatform {
|
||||
/// Creates a new plugin implementation instance.
|
||||
QuickActionsAndroid({
|
||||
@visibleForTesting AndroidQuickActionsApi? api,
|
||||
}) : _hostApi = api ?? AndroidQuickActionsApi();
|
||||
|
||||
final AndroidQuickActionsApi _hostApi;
|
||||
|
||||
/// Registers this class as the default instance of [QuickActionsPlatform].
|
||||
static void registerWith() {
|
||||
QuickActionsPlatform.instance = QuickActionsAndroid();
|
||||
}
|
||||
|
||||
/// The MethodChannel that is being used by this implementation of the plugin.
|
||||
@visibleForTesting
|
||||
MethodChannel get channel => _channel;
|
||||
|
||||
@override
|
||||
Future<void> initialize(QuickActionHandler handler) async {
|
||||
channel.setMethodCallHandler((MethodCall call) async {
|
||||
assert(call.method == 'launch');
|
||||
handler(call.arguments as String);
|
||||
});
|
||||
final String? action =
|
||||
await channel.invokeMethod<String?>('getLaunchAction');
|
||||
final _QuickActionHandlerApi quickActionsHandlerApi =
|
||||
_QuickActionHandlerApi();
|
||||
AndroidQuickActionsFlutterApi.setup(quickActionsHandlerApi);
|
||||
_handler = handler;
|
||||
final String? action = await _hostApi.getLaunchAction();
|
||||
if (action != null) {
|
||||
handler(action);
|
||||
_handler(action);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setShortcutItems(List<ShortcutItem> items) async {
|
||||
final List<Map<String, String?>> itemsList =
|
||||
items.map(_serializeItem).toList();
|
||||
await channel.invokeMethod<void>('setShortcutItems', itemsList);
|
||||
await _hostApi.setShortcutItems(
|
||||
items.map(_shortcutItemToShortcutItemMessage).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> clearShortcutItems() =>
|
||||
channel.invokeMethod<void>('clearShortcutItems');
|
||||
Future<void> clearShortcutItems() => _hostApi.clearShortcutItems();
|
||||
|
||||
Map<String, String?> _serializeItem(ShortcutItem item) {
|
||||
return <String, String?>{
|
||||
'type': item.type,
|
||||
'localizedTitle': item.localizedTitle,
|
||||
'icon': item.icon,
|
||||
};
|
||||
ShortcutItemMessage _shortcutItemToShortcutItemMessage(ShortcutItem item) {
|
||||
return ShortcutItemMessage(
|
||||
type: item.type,
|
||||
localizedTitle: item.localizedTitle,
|
||||
icon: item.icon,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _QuickActionHandlerApi extends AndroidQuickActionsFlutterApi {
|
||||
@override
|
||||
void launchAction(String action) {
|
||||
_handler(action);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,183 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
// Autogenerated from Pigeon (v11.0.1), do not edit directly.
|
||||
// See also: https://pub.dev/packages/pigeon
|
||||
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
|
||||
|
||||
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// Home screen quick-action shortcut item.
|
||||
class ShortcutItemMessage {
|
||||
ShortcutItemMessage({
|
||||
required this.type,
|
||||
required this.localizedTitle,
|
||||
this.icon,
|
||||
});
|
||||
|
||||
/// The identifier of this item; should be unique within the app.
|
||||
String type;
|
||||
|
||||
/// Localized title of the item.
|
||||
String localizedTitle;
|
||||
|
||||
/// Name of native resource to be displayed as the icon for this item.
|
||||
String? icon;
|
||||
|
||||
Object encode() {
|
||||
return <Object?>[
|
||||
type,
|
||||
localizedTitle,
|
||||
icon,
|
||||
];
|
||||
}
|
||||
|
||||
static ShortcutItemMessage decode(Object result) {
|
||||
result as List<Object?>;
|
||||
return ShortcutItemMessage(
|
||||
type: result[0]! as String,
|
||||
localizedTitle: result[1]! as String,
|
||||
icon: result[2] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AndroidQuickActionsApiCodec extends StandardMessageCodec {
|
||||
const _AndroidQuickActionsApiCodec();
|
||||
@override
|
||||
void writeValue(WriteBuffer buffer, Object? value) {
|
||||
if (value is ShortcutItemMessage) {
|
||||
buffer.putUint8(128);
|
||||
writeValue(buffer, value.encode());
|
||||
} else {
|
||||
super.writeValue(buffer, value);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||
switch (type) {
|
||||
case 128:
|
||||
return ShortcutItemMessage.decode(readValue(buffer)!);
|
||||
default:
|
||||
return super.readValueOfType(type, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AndroidQuickActionsApi {
|
||||
/// Constructor for [AndroidQuickActionsApi]. The [binaryMessenger] named argument is
|
||||
/// available for dependency injection. If it is left null, the default
|
||||
/// BinaryMessenger will be used which routes to the host platform.
|
||||
AndroidQuickActionsApi({BinaryMessenger? binaryMessenger})
|
||||
: _binaryMessenger = binaryMessenger;
|
||||
final BinaryMessenger? _binaryMessenger;
|
||||
|
||||
static const MessageCodec<Object?> codec = _AndroidQuickActionsApiCodec();
|
||||
|
||||
/// Checks for, and returns the action that launched the app.
|
||||
Future<String?> getLaunchAction() async {
|
||||
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsApi.getLaunchAction',
|
||||
codec,
|
||||
binaryMessenger: _binaryMessenger);
|
||||
final List<Object?>? replyList = await channel.send(null) as List<Object?>?;
|
||||
if (replyList == null) {
|
||||
throw PlatformException(
|
||||
code: 'channel-error',
|
||||
message: 'Unable to establish connection on channel.',
|
||||
);
|
||||
} else if (replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: replyList[0]! as String,
|
||||
message: replyList[1] as String?,
|
||||
details: replyList[2],
|
||||
);
|
||||
} else {
|
||||
return (replyList[0] as String?);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the dynamic shortcuts for the app.
|
||||
Future<void> setShortcutItems(
|
||||
List<ShortcutItemMessage?> arg_itemsList) async {
|
||||
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsApi.setShortcutItems',
|
||||
codec,
|
||||
binaryMessenger: _binaryMessenger);
|
||||
final List<Object?>? replyList =
|
||||
await channel.send(<Object?>[arg_itemsList]) as List<Object?>?;
|
||||
if (replyList == null) {
|
||||
throw PlatformException(
|
||||
code: 'channel-error',
|
||||
message: 'Unable to establish connection on channel.',
|
||||
);
|
||||
} else if (replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: replyList[0]! as String,
|
||||
message: replyList[1] as String?,
|
||||
details: replyList[2],
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes all dynamic shortcuts.
|
||||
Future<void> clearShortcutItems() async {
|
||||
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsApi.clearShortcutItems',
|
||||
codec,
|
||||
binaryMessenger: _binaryMessenger);
|
||||
final List<Object?>? replyList = await channel.send(null) as List<Object?>?;
|
||||
if (replyList == null) {
|
||||
throw PlatformException(
|
||||
code: 'channel-error',
|
||||
message: 'Unable to establish connection on channel.',
|
||||
);
|
||||
} else if (replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: replyList[0]! as String,
|
||||
message: replyList[1] as String?,
|
||||
details: replyList[2],
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AndroidQuickActionsFlutterApi {
|
||||
static const MessageCodec<Object?> codec = StandardMessageCodec();
|
||||
|
||||
/// Sends a string representing a shortcut from the native platform to the app.
|
||||
void launchAction(String action);
|
||||
|
||||
static void setup(AndroidQuickActionsFlutterApi? api,
|
||||
{BinaryMessenger? binaryMessenger}) {
|
||||
{
|
||||
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsFlutterApi.launchAction',
|
||||
codec,
|
||||
binaryMessenger: binaryMessenger);
|
||||
if (api == null) {
|
||||
channel.setMessageHandler(null);
|
||||
} else {
|
||||
channel.setMessageHandler((Object? message) async {
|
||||
assert(message != null,
|
||||
'Argument for dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsFlutterApi.launchAction was null.');
|
||||
final List<Object?> args = (message as List<Object?>?)!;
|
||||
final String? arg_action = (args[0] as String?);
|
||||
assert(arg_action != null,
|
||||
'Argument for dev.flutter.pigeon.quick_actions_android.AndroidQuickActionsFlutterApi.launchAction was null, expected non-null String.');
|
||||
api.launchAction(arg_action!);
|
||||
return;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file.
|
@ -0,0 +1,52 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:pigeon/pigeon.dart';
|
||||
|
||||
@ConfigurePigeon(PigeonOptions(
|
||||
dartOut: 'lib/src/messages.g.dart',
|
||||
javaOut:
|
||||
'android/src/main/java/io/flutter/plugins/quickactions/Messages.java',
|
||||
javaOptions: JavaOptions(
|
||||
package: 'io.flutter.plugins.quickactions',
|
||||
),
|
||||
copyrightHeader: 'pigeons/copyright.txt',
|
||||
))
|
||||
|
||||
/// Home screen quick-action shortcut item.
|
||||
class ShortcutItemMessage {
|
||||
ShortcutItemMessage(
|
||||
this.type,
|
||||
this.localizedTitle,
|
||||
this.icon,
|
||||
);
|
||||
|
||||
/// The identifier of this item; should be unique within the app.
|
||||
String type;
|
||||
|
||||
/// Localized title of the item.
|
||||
String localizedTitle;
|
||||
|
||||
/// Name of native resource to be displayed as the icon for this item.
|
||||
String? icon;
|
||||
}
|
||||
|
||||
@HostApi()
|
||||
abstract class AndroidQuickActionsApi {
|
||||
/// Checks for, and returns the action that launched the app.
|
||||
String? getLaunchAction();
|
||||
|
||||
/// Sets the dynamic shortcuts for the app.
|
||||
@async
|
||||
void setShortcutItems(List<ShortcutItemMessage> itemsList);
|
||||
|
||||
/// Removes all dynamic shortcuts.
|
||||
void clearShortcutItems();
|
||||
}
|
||||
|
||||
@FlutterApi()
|
||||
abstract class AndroidQuickActionsFlutterApi {
|
||||
/// Sends a string representing a shortcut from the native platform to the app.
|
||||
void launchAction(String action);
|
||||
}
|
@ -2,7 +2,7 @@ name: quick_actions_android
|
||||
description: An implementation for the Android platform of the Flutter `quick_actions` plugin.
|
||||
repository: https://github.com/flutter/packages/tree/main/packages/quick_actions/quick_actions_android
|
||||
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22
|
||||
version: 1.0.8
|
||||
version: 1.0.9
|
||||
|
||||
environment:
|
||||
sdk: ">=2.19.0 <4.0.0"
|
||||
@ -27,6 +27,7 @@ dev_dependencies:
|
||||
sdk: flutter
|
||||
integration_test:
|
||||
sdk: flutter
|
||||
pigeon: ^11.0.1
|
||||
plugin_platform_interface: ^2.1.2
|
||||
|
||||
topics:
|
||||
|
@ -4,169 +4,104 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:quick_actions_android/quick_actions_android.dart';
|
||||
import 'package:quick_actions_android/src/messages.g.dart';
|
||||
import 'package:quick_actions_platform_interface/quick_actions_platform_interface.dart';
|
||||
|
||||
const String LAUNCH_ACTION_STRING = 'aString';
|
||||
|
||||
/// Conversion tool to change [ShortcutItemMessage] back to [ShortcutItem]
|
||||
ShortcutItem shortcutItemMessageToShortcutItem(ShortcutItemMessage item) {
|
||||
return ShortcutItem(
|
||||
type: item.type,
|
||||
localizedTitle: item.localizedTitle,
|
||||
icon: item.icon,
|
||||
);
|
||||
}
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('$QuickActionsAndroid', () {
|
||||
late List<MethodCall> log;
|
||||
final _FakeQuickActionsApi api = _FakeQuickActionsApi();
|
||||
final QuickActionsAndroid quickActions = QuickActionsAndroid(api: api);
|
||||
|
||||
setUp(() {
|
||||
log = <MethodCall>[];
|
||||
test('registerWith() registers correct instance', () {
|
||||
QuickActionsAndroid.registerWith();
|
||||
expect(QuickActionsPlatform.instance, isA<QuickActionsAndroid>());
|
||||
});
|
||||
|
||||
group('#initialize', () {
|
||||
test('passes getLaunchAction on launch method', () {
|
||||
quickActions.initialize((String type) {});
|
||||
|
||||
expect(api.getLaunchActionCalled, true);
|
||||
});
|
||||
|
||||
QuickActionsAndroid buildQuickActionsPlugin() {
|
||||
final QuickActionsAndroid quickActions = QuickActionsAndroid();
|
||||
_ambiguate(TestDefaultBinaryMessengerBinding.instance)!
|
||||
.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(quickActions.channel,
|
||||
(MethodCall methodCall) async {
|
||||
log.add(methodCall);
|
||||
return '';
|
||||
});
|
||||
test('initialize', () async {
|
||||
final Completer<bool> quickActionsHandler = Completer<bool>();
|
||||
await quickActions.initialize((_) => quickActionsHandler.complete(true));
|
||||
|
||||
return quickActions;
|
||||
}
|
||||
|
||||
test('registerWith() registers correct instance', () {
|
||||
QuickActionsAndroid.registerWith();
|
||||
expect(QuickActionsPlatform.instance, isA<QuickActionsAndroid>());
|
||||
});
|
||||
|
||||
group('#initialize', () {
|
||||
test('passes getLaunchAction on launch method', () {
|
||||
final QuickActionsAndroid quickActions = buildQuickActionsPlugin();
|
||||
quickActions.initialize((String type) {});
|
||||
|
||||
expect(
|
||||
log,
|
||||
<Matcher>[
|
||||
isMethodCall('getLaunchAction', arguments: null),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test('initialize', () async {
|
||||
final QuickActionsAndroid quickActions = buildQuickActionsPlugin();
|
||||
final Completer<bool> quickActionsHandler = Completer<bool>();
|
||||
await quickActions
|
||||
.initialize((_) => quickActionsHandler.complete(true));
|
||||
expect(
|
||||
log,
|
||||
<Matcher>[
|
||||
isMethodCall('getLaunchAction', arguments: null),
|
||||
],
|
||||
);
|
||||
log.clear();
|
||||
|
||||
expect(quickActionsHandler.future, completion(isTrue));
|
||||
});
|
||||
});
|
||||
|
||||
group('#setShortCutItems', () {
|
||||
test('passes shortcutItem through channel', () {
|
||||
final QuickActionsAndroid quickActions = buildQuickActionsPlugin();
|
||||
quickActions.initialize((String type) {});
|
||||
quickActions.setShortcutItems(<ShortcutItem>[
|
||||
const ShortcutItem(
|
||||
type: 'test', localizedTitle: 'title', icon: 'icon.svg')
|
||||
]);
|
||||
|
||||
expect(
|
||||
log,
|
||||
<Matcher>[
|
||||
isMethodCall('getLaunchAction', arguments: null),
|
||||
isMethodCall('setShortcutItems', arguments: <Map<String, String>>[
|
||||
<String, String>{
|
||||
'type': 'test',
|
||||
'localizedTitle': 'title',
|
||||
'icon': 'icon.svg',
|
||||
}
|
||||
]),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test('setShortcutItems with demo data', () async {
|
||||
const String type = 'type';
|
||||
const String localizedTitle = 'localizedTitle';
|
||||
const String icon = 'icon';
|
||||
final QuickActionsAndroid quickActions = buildQuickActionsPlugin();
|
||||
await quickActions.setShortcutItems(
|
||||
const <ShortcutItem>[
|
||||
ShortcutItem(type: type, localizedTitle: localizedTitle, icon: icon)
|
||||
],
|
||||
);
|
||||
expect(
|
||||
log,
|
||||
<Matcher>[
|
||||
isMethodCall(
|
||||
'setShortcutItems',
|
||||
arguments: <Map<String, String>>[
|
||||
<String, String>{
|
||||
'type': type,
|
||||
'localizedTitle': localizedTitle,
|
||||
'icon': icon,
|
||||
}
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
log.clear();
|
||||
});
|
||||
});
|
||||
|
||||
group('#clearShortCutItems', () {
|
||||
test('send clearShortcutItems through channel', () {
|
||||
final QuickActionsAndroid quickActions = buildQuickActionsPlugin();
|
||||
quickActions.initialize((String type) {});
|
||||
quickActions.clearShortcutItems();
|
||||
|
||||
expect(
|
||||
log,
|
||||
<Matcher>[
|
||||
isMethodCall('getLaunchAction', arguments: null),
|
||||
isMethodCall('clearShortcutItems', arguments: null),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test('clearShortcutItems', () {
|
||||
final QuickActionsAndroid quickActions = buildQuickActionsPlugin();
|
||||
quickActions.clearShortcutItems();
|
||||
expect(
|
||||
log,
|
||||
<Matcher>[
|
||||
isMethodCall('clearShortcutItems', arguments: null),
|
||||
],
|
||||
);
|
||||
log.clear();
|
||||
});
|
||||
expect(quickActionsHandler.future, completion(isTrue));
|
||||
});
|
||||
});
|
||||
|
||||
group('$ShortcutItem', () {
|
||||
test('Shortcut item can be constructed', () {
|
||||
const String type = 'type';
|
||||
const String localizedTitle = 'title';
|
||||
const String icon = 'foo';
|
||||
test('setShortCutItems', () async {
|
||||
await quickActions.initialize((String type) {});
|
||||
const ShortcutItem item =
|
||||
ShortcutItem(type: 'test', localizedTitle: 'title', icon: 'icon.svg');
|
||||
await quickActions.setShortcutItems(<ShortcutItem>[item]);
|
||||
|
||||
const ShortcutItem item =
|
||||
ShortcutItem(type: type, localizedTitle: localizedTitle, icon: icon);
|
||||
expect(api.items.first.type, item.type);
|
||||
expect(api.items.first.localizedTitle, item.localizedTitle);
|
||||
expect(api.items.first.icon, item.icon);
|
||||
});
|
||||
|
||||
expect(item.type, type);
|
||||
expect(item.localizedTitle, localizedTitle);
|
||||
expect(item.icon, icon);
|
||||
});
|
||||
test('clearShortCutItems', () {
|
||||
quickActions.initialize((String type) {});
|
||||
const ShortcutItem item =
|
||||
ShortcutItem(type: 'test', localizedTitle: 'title', icon: 'icon.svg');
|
||||
quickActions.setShortcutItems(<ShortcutItem>[item]);
|
||||
quickActions.clearShortcutItems();
|
||||
|
||||
expect(api.items.isEmpty, true);
|
||||
});
|
||||
|
||||
test('Shortcut item can be constructed', () {
|
||||
const String type = 'type';
|
||||
const String localizedTitle = 'title';
|
||||
const String icon = 'foo';
|
||||
|
||||
const ShortcutItem item =
|
||||
ShortcutItem(type: type, localizedTitle: localizedTitle, icon: icon);
|
||||
|
||||
expect(item.type, type);
|
||||
expect(item.localizedTitle, localizedTitle);
|
||||
expect(item.icon, icon);
|
||||
});
|
||||
}
|
||||
|
||||
/// This allows a value of type T or T? to be treated as a value of type T?.
|
||||
///
|
||||
/// We use this so that APIs that have become non-nullable can still be used
|
||||
/// with `!` and `?` on the stable branch.
|
||||
T? _ambiguate<T>(T? value) => value;
|
||||
class _FakeQuickActionsApi implements AndroidQuickActionsApi {
|
||||
List<ShortcutItem> items = <ShortcutItem>[];
|
||||
bool getLaunchActionCalled = false;
|
||||
|
||||
@override
|
||||
Future<void> clearShortcutItems() async {
|
||||
items = <ShortcutItem>[];
|
||||
return;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> getLaunchAction() async {
|
||||
getLaunchActionCalled = true;
|
||||
return LAUNCH_ACTION_STRING;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setShortcutItems(List<ShortcutItemMessage?> itemsList) async {
|
||||
await clearShortcutItems();
|
||||
for (final ShortcutItemMessage? element in itemsList) {
|
||||
items.add(shortcutItemMessageToShortcutItem(element!));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user