Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

[quick_actions] Support v2 android embedder. #2167

Merged
merged 20 commits into from
Nov 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion packages/quick_actions/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.3.3

* Support Android V2 embedding.
* Add e2e tests.
* Migrate to using the new e2e test binding.

## 0.3.2+4

* Remove AndroidX warnings.
Expand All @@ -7,6 +13,7 @@
* Define clang module for iOS.

## 0.3.2+2

* Fix bug that would make the shortcut not open on Android.
* Report shortcut used on Android.
* Improves example.
Expand All @@ -17,7 +24,7 @@

## 0.3.2

* Fixed the quick actions launch on Android when the app is killed.
* Fixed the quick actions launch on Android when the app is killed.

## 0.3.1

Expand Down
25 changes: 25 additions & 0 deletions packages/quick_actions/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,28 @@ android {
disable 'InvalidPackage'
}
}

afterEvaluate {
def containsEmbeddingDependencies = false
for (def configuration : configurations.all) {
for (def dependency : configuration.dependencies) {
if (dependency.group == 'io.flutter' &&
dependency.name.startsWith('flutter_embedding') &&
dependency.isTransitive())
{
containsEmbeddingDependencies = true
break
}
}
}
if (!containsEmbeddingDependencies) {
android {
dependencies {
def lifecycle_version = "1.1.1"
compileOnly "android.arch.lifecycle:runtime:$lifecycle_version"
compileOnly "android.arch.lifecycle:common:$lifecycle_version"
compileOnly "android.arch.lifecycle:common-java8:$lifecycle_version"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright 2019 The Chromium 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 io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler {

private static final String CHANNEL_ID = "plugins.flutter.io/quick_actions";
private static final String EXTRA_ACTION = "some unique action key";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should "some unique action key" be written here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have enough context to give input here. It seems this is introduced in 63609c9
and @collinjackson or @BugsBunnyBR might be able to give some inputs on this.
Anyways, this is not related to this migration. I suggest we create new issue and fix it in a later PR if needs to be fixed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is just a unique string, the value itself does not matter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BugsBunnyBR We can make the value of the String unambiguous so it is more readable.


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(MethodCall call, 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":
List<Map<String, String>> serializedShortcuts = call.arguments();
List<ShortcutInfo> shortcuts = deserializeShortcuts(serializedShortcuts);
shortcutManager.setDynamicShortcuts(shortcuts);
break;
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;
}

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_CLEAR_TASK);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,130 +4,72 @@

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 io.flutter.plugin.common.MethodCall;
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.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/** QuickActionsPlugin */
public class QuickActionsPlugin implements MethodCallHandler {
public class QuickActionsPlugin implements FlutterPlugin, ActivityAware {
private static final String CHANNEL_ID = "plugins.flutter.io/quick_actions";
private static final String EXTRA_ACTION = "some unique action key";

private final Registrar registrar;

private QuickActionsPlugin(Registrar registrar) {
this.registrar = registrar;
}
private MethodChannel channel;
private MethodCallHandlerImpl handler;

/**
* Plugin registration.
*
* <p>Must be called when the application is created.
*/
public static void registerWith(Registrar registrar) {
final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL_ID);
channel.setMethodCallHandler(new QuickActionsPlugin(registrar));
final QuickActionsPlugin plugin = new QuickActionsPlugin();
plugin.setupChannel(registrar.messenger(), registrar.context(), registrar.activity());
}

@Override
public void onMethodCall(MethodCall call, 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;
}
Context context = registrar.context();
ShortcutManager shortcutManager =
(ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE);
switch (call.method) {
case "setShortcutItems":
List<Map<String, String>> serializedShortcuts = call.arguments();
List<ShortcutInfo> shortcuts = deserializeShortcuts(serializedShortcuts);
shortcutManager.setDynamicShortcuts(shortcuts);
break;
case "clearShortcutItems":
shortcutManager.removeAllDynamicShortcuts();
break;
case "getLaunchAction":
final Intent intent = registrar.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);
public void onAttachedToEngine(FlutterPluginBinding binding) {
setupChannel(
binding.getFlutterEngine().getDartExecutor(), binding.getApplicationContext(), null);
}

@TargetApi(Build.VERSION_CODES.N_MR1)
private List<ShortcutInfo> deserializeShortcuts(List<Map<String, String>> shortcuts) {
final List<ShortcutInfo> shortcutInfos = new ArrayList<>();
final Context context = registrar.context();

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);
@Override
public void onDetachedFromEngine(FlutterPluginBinding binding) {
teardownChannel();
}

if (resourceId > 0) {
shortcutBuilder.setIcon(Icon.createWithResource(context, resourceId));
}
@Override
public void onAttachedToActivity(ActivityPluginBinding binding) {
handler.setActivity(binding.getActivity());
}

final ShortcutInfo shortcutInfo =
shortcutBuilder.setLongLabel(title).setShortLabel(title).setIntent(intent).build();
shortcutInfos.add(shortcutInfo);
}
return shortcutInfos;
@Override
public void onDetachedFromActivity() {
handler.setActivity(null);
}

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);
@Override
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
onAttachedToActivity(binding);
}

if (resourceId == 0) {
return res.getIdentifier(icon, "mipmap", packageName);
} else {
return resourceId;
}
@Override
public void onDetachedFromActivityForConfigChanges() {
onDetachedFromActivity();
}

private Intent getIntentToOpenMainActivity(String type) {
final Context context = registrar.context();
final String packageName = context.getPackageName();
private void setupChannel(BinaryMessenger messenger, Context context, Activity activity) {
channel = new MethodChannel(messenger, CHANNEL_ID);
handler = new MethodCallHandlerImpl(context, activity);
channel.setMethodCallHandler(handler);
}

return context
.getPackageManager()
.getLaunchIntentForPackage(packageName)
.setAction(Intent.ACTION_RUN)
.putExtra(EXTRA_ACTION, type)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
private void teardownChannel() {
channel.setMethodCallHandler(null);
channel = null;
handler = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,22 @@
<application android:name="io.flutter.app.FlutterApplication"
android:label="quick_actions_example"
android:icon="@mipmap/ic_launcher">
<activity android:name=".MainActivity"
<activity android:name=".EmbeddingV1Activity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
android:windowSoftInputMode="adjustResize"
android:exported="true">
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true"/>
</activity>
<activity android:name=".MainActivity"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
Expand Down
Loading