Support new ACTION_CREATE_SHORTCUT in sample apps

Test: manual tests
Change-Id: I188b846152ccf7fdb143be69d6375ece4f61211b
This commit is contained in:
Makoto Onuki
2017-01-20 12:16:30 -08:00
parent 02a314b14c
commit 64ef7bcd6f
7 changed files with 359 additions and 196 deletions

View File

@@ -15,201 +15,54 @@
*/
package com.example.android.pm.shortcutlauncherdemo;
import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.ShortcutQuery;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class AppListFragment extends MyBaseListFragment {
private AppAdapter mAdapter;
public class AppListFragment extends BaseActivityListFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAdapter = new AppAdapter(getActivity());
setListAdapter(mAdapter);
protected List<LauncherActivityInfo> getActivities(UserHandle user) {
return mLauncherApps.getActivityList(null, user);
}
@Override
protected void refreshList() {
Log.d(Global.TAG, "Loading apps and shortcuts...");
protected void onBindAction2(Button v, LauncherActivityInfo ai, OnClickListener listener) {
try {
if (mUserManager.isUserUnlocked(ai.getUser())
&& mLauncherApps.hasShortcutHostPermission()) {
mQuery.setPackage(ai.getComponentName().getPackageName());
mQuery.setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC
| ShortcutQuery.FLAG_MATCH_PINNED
| ShortcutQuery.FLAG_MATCH_MANIFEST
| ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY);
mQuery.setActivity(ai.getComponentName());
final List<LauncherActivityInfo> apps = new ArrayList<>();
for (UserHandle user : mLauncherApps.getProfiles()) {
apps.addAll(mLauncherApps.getActivityList(null, user));
}
Collections.sort(apps, sLauncherIconComparator);
Log.d(Global.TAG, "Apps and shortcuts loaded.");
mAdapter.setList(apps);
}
private static final Comparator<LauncherActivityInfo> sLauncherIconComparator =
(LauncherActivityInfo l1, LauncherActivityInfo l2) -> {
int ret = 0;
ret = l1.getLabel().toString().compareTo(l2.getLabel().toString());
if (ret != 0) return ret;
// TODO Don't rely on hashCode being the user-id.
ret = l1.getUser().hashCode() - l2.getUser().hashCode();
if (ret != 0) return ret;
return 0;
};
public class AppAdapter extends BaseAdapter implements OnClickListener {
private final Context mContext;
private final LayoutInflater mInflater;
private final UserManager mUserManager;
private final LauncherApps mLauncherApps;
private List<LauncherActivityInfo> mList;
public AppAdapter(Context context) {
mContext = context;
mInflater = mContext.getSystemService(LayoutInflater.class);
mUserManager = mContext.getSystemService(UserManager.class);
mLauncherApps = mContext.getSystemService(LauncherApps.class);
}
public void setList(List<LauncherActivityInfo> list) {
mList = list;
notifyDataSetChanged();
}
@Override
public int getCount() {
return mList == null ? 0 : mList.size();
}
@Override
public LauncherActivityInfo getItem(int position) {
return mList == null ? null : mList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public boolean areAllItemsEnabled() {
return true;
}
@Override
public boolean isEnabled(int position) {
return true;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final View view;
if (convertView != null) {
view = convertView;
} else {
view = mInflater.inflate(R.layout.list_item, null);
}
bindView(view, getItem(position));
return view;
}
public void bindView(View view, LauncherActivityInfo ai) {
{
final View v = view.findViewById(R.id.launch);
v.setTag(ai);
v.setOnClickListener(this);
v.setVisibility(View.VISIBLE);
}
{
final Button v = (Button) view.findViewById(R.id.action2);
v.setTag(ai);
v.setVisibility(View.INVISIBLE);
try {
if (mUserManager.isUserUnlocked(ai.getUser())
&& mLauncherApps.hasShortcutHostPermission()) {
mQuery.setPackage(ai.getComponentName().getPackageName());
mQuery.setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC
| ShortcutQuery.FLAG_MATCH_PINNED
| ShortcutQuery.FLAG_MATCH_MANIFEST
| ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY);
mQuery.setActivity(ai.getComponentName());
if (mLauncherApps.getShortcuts(mQuery, ai.getUser()).size() > 0) {
v.setOnClickListener(this);
v.setVisibility(View.VISIBLE);
v.setText("Shortcuts");
}
}
} catch (Exception e) {
Log.w(Global.TAG, "Caught exception", e);
if (mLauncherApps.getShortcuts(mQuery, ai.getUser()).size() > 0) {
v.setOnClickListener(listener);
v.setVisibility(View.VISIBLE);
v.setText("Shortcuts");
}
}
final TextView line1 = (TextView) view.findViewById(R.id.line1);
final TextView line2 = (TextView) view.findViewById(R.id.line2);
line1.setText(ai.getLabel());
// TODO Do it on worker thread
final Drawable icon = ai.getBadgedIcon(DisplayMetrics.DENSITY_DEFAULT);
final ImageView image = (ImageView) view.findViewById(R.id.image);
image.setImageDrawable(icon);
}
@Override
public void onClick (View v){
final LauncherActivityInfo ai = (LauncherActivityInfo) v.getTag();
switch (v.getId()) {
case R.id.launch:
try {
mLauncherApps.startMainActivity(ai.getComponentName(), ai.getUser(),
null, null);
} catch (Exception e) {
Global.showToast(getContext(), e.getMessage());
}
return;
case R.id.action2:
showShortcutsForPackage(ai);
return;
}
} catch (Exception e) {
Log.w(Global.TAG, "Caught exception", e);
}
}
private void showShortcutsForPackage(LauncherActivityInfo ai) {
@Override
protected void onLaunch(LauncherActivityInfo ai) {
mLauncherApps.startMainActivity(ai.getComponentName(), ai.getUser(), null, null);
}
@Override
protected void onAction2(LauncherActivityInfo ai) {
final Intent i = PackageShortcutActivity.getLaunchIntent(
getActivity(),
ai.getComponentName().getPackageName(),
@@ -217,5 +70,4 @@ public class AppListFragment extends MyBaseListFragment {
ai.getUser(),
ai.getLabel());
getActivity().startActivity(i);
}
}
}}

View File

@@ -0,0 +1,212 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.pm.shortcutlauncherdemo;
import android.content.Context;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public abstract class BaseActivityListFragment extends MyBaseListFragment {
private AppAdapter mAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAdapter = new AppAdapter(getActivity());
setListAdapter(mAdapter);
}
@Override
protected void refreshList() {
Log.d(Global.TAG, "Loading apps and shortcuts...");
final List<LauncherActivityInfo> apps = new ArrayList<>();
try {
for (UserHandle user : mLauncherApps.getProfiles()) {
if (mUserManager.isUserUnlocked(user)) {
apps.addAll(getActivities(user));
}
}
Collections.sort(apps, sLauncherIconComparator);
} catch (Exception e) {
Global.showToast(getContext(), e.getMessage());
}
Log.d(Global.TAG, "Apps and shortcuts loaded. (count=" + apps.size() + ")");
mAdapter.setList(apps);
}
protected abstract List<LauncherActivityInfo> getActivities(UserHandle user);
private static final Comparator<LauncherActivityInfo> sLauncherIconComparator =
(LauncherActivityInfo l1, LauncherActivityInfo l2) -> {
int ret = 0;
ret = l1.getLabel().toString().compareTo(l2.getLabel().toString());
if (ret != 0) return ret;
// TODO Don't rely on hashCode being the user-id.
ret = l1.getUser().hashCode() - l2.getUser().hashCode();
if (ret != 0) return ret;
return 0;
};
private class AppAdapter extends BaseAdapter implements OnClickListener {
private final Context mContext;
private final LayoutInflater mInflater;
private List<LauncherActivityInfo> mList;
public AppAdapter(Context context) {
mContext = context;
mInflater = mContext.getSystemService(LayoutInflater.class);
mUserManager = mContext.getSystemService(UserManager.class);
mLauncherApps = mContext.getSystemService(LauncherApps.class);
}
public void setList(List<LauncherActivityInfo> list) {
mList = list;
notifyDataSetChanged();
}
@Override
public int getCount() {
return mList == null ? 0 : mList.size();
}
@Override
public LauncherActivityInfo getItem(int position) {
return mList == null ? null : mList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public boolean areAllItemsEnabled() {
return true;
}
@Override
public boolean isEnabled(int position) {
return true;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final View view;
if (convertView != null) {
view = convertView;
} else {
view = mInflater.inflate(R.layout.list_item, null);
}
bindView(view, getItem(position));
return view;
}
public void bindView(View view, LauncherActivityInfo ai) {
{
final View v = view.findViewById(R.id.launch);
v.setTag(ai);
v.setOnClickListener(this);
v.setVisibility(View.VISIBLE);
}
{
final Button v = (Button) view.findViewById(R.id.action2);
v.setTag(ai);
v.setVisibility(View.INVISIBLE);
try {
onBindAction2(v, ai, this);
} catch (Exception e) {
Log.w(Global.TAG, "Caught exception", e);
}
}
final TextView line1 = (TextView) view.findViewById(R.id.line1);
final TextView line2 = (TextView) view.findViewById(R.id.line2);
line1.setText(ai.getLabel());
// TODO Do it on worker thread
final Drawable icon = ai.getBadgedIcon(DisplayMetrics.DENSITY_DEFAULT);
final ImageView image = (ImageView) view.findViewById(R.id.image);
image.setImageDrawable(icon);
}
@Override
public void onClick(View v) {
final LauncherActivityInfo ai = (LauncherActivityInfo) v.getTag();
switch (v.getId()) {
case R.id.launch:
try {
onLaunch(ai);
} catch (Exception e) {
Global.showToast(getContext(), e.getMessage());
}
return;
case R.id.action2:
try {
onAction2(ai);
} catch (Exception e) {
Global.showToast(getContext(), e.getMessage());
}
return;
}
}
}
protected void onBindAction2(Button v, LauncherActivityInfo ai,
OnClickListener listener) {
}
protected abstract void onLaunch(LauncherActivityInfo ai);
protected void onAction2(LauncherActivityInfo ai) {
}
}

View File

@@ -47,6 +47,7 @@ public class ShortcutLauncherMain extends Activity {
ab.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
ab.addTab(ab.newTab().setText("App list").setTabListener(mTabListener));
ab.addTab(ab.newTab().setText("Pinned shortcuts").setTabListener(mTabListener));
ab.addTab(ab.newTab().setText("Create shortcuts").setTabListener(mTabListener));
}
@Override
@@ -91,13 +92,15 @@ public class ShortcutLauncherMain extends Activity {
null /* means "all profiles" of this user*/,
/* showDetails =*/ true
);
case 2:
return new ShortcutTemplateListFragment();
}
return null;
}
@Override
public int getCount() {
return 2;
return 3;
}
@Override

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.pm.shortcutlauncherdemo;
import android.app.Activity;
import android.content.Intent;
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps.PinItemRequest;
import android.os.UserHandle;
import android.util.Log;
import java.util.List;
public class ShortcutTemplateListFragment extends BaseActivityListFragment {
private static final String TAG = "ShortcutTemplateListFragment";
private static final int REQUEST_SHORTCUT = 1;
@Override
protected List<LauncherActivityInfo> getActivities(UserHandle user) {
return mLauncherApps.getShortcutConfigActivityList(null, user);
}
@Override
protected void onLaunch(LauncherActivityInfo ai) {
final IntentSender is = mLauncherApps.getShortcutConfigActivityIntent(ai);
try {
startIntentSenderForResult(is, REQUEST_SHORTCUT, null, 0,0, 0, null);
} catch (SendIntentException e) {
Log.e(TAG, "Couldn't start activity", e);
Global.showToast(getActivity(), "Couldn't start activity: " + e.getMessage());
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK || data == null) {
return;
}
switch (requestCode) {
case REQUEST_SHORTCUT:
final PinItemRequest req = mLauncherApps.getPinItemRequest(data);
if (req == null) {
Global.showToast(getActivity(),
"App doesn't support app shortcut (only supports \"legacy\" ones)");
} else {
req.accept();
}
break;
}
}
}

View File

@@ -32,6 +32,9 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.CREATE_SHORTCUT" />
</intent-filter>
<meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
</activity>
<receiver android:name="MyReceiver"

View File

@@ -15,6 +15,7 @@
*/
package com.example.android.shortcutsample;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ListActivity;
import android.content.Context;
@@ -70,7 +71,9 @@ public class Main extends ListActivity implements OnClickListener {
if (ACTION_ADD_WEBSITE.equals(getIntent().getAction())) {
// Invoked via the manifest shortcut.
addWebSite(/* forPin=*/ false);
addWebSite(/* forPin=*/ false, /* forResult= */ false);
} else if (Intent.ACTION_CREATE_SHORTCUT.equals(getIntent().getAction())) {
addWebSite(/* forPin=*/ true, /* forResult= */ true);
}
mAdapter = new MyAdapter(this.getApplicationContext());
@@ -100,17 +103,17 @@ public class Main extends ListActivity implements OnClickListener {
* Handle the add button.
*/
public void onAddPressed(View v) {
addWebSite(/* forPin=*/ false);
addWebSite(/* forPin=*/ false, /* forResult= */ false);
}
/**
* Handle the add button.
*/
public void onRequestNewPinPressed(View v) {
addWebSite(/* forPin=*/ true);
addWebSite(/* forPin=*/ true, /* forResult= */ false);
}
private void addWebSite(boolean forPin) {
private void addWebSite(boolean forPin, boolean forResult) {
Log.i(TAG, "addWebSite forPin=" + forPin);
// This is important. This allows the launcher to build a prediction model.
@@ -128,25 +131,49 @@ public class Main extends ListActivity implements OnClickListener {
.setPositiveButton("Add", (dialog, whichButton) -> {
final String url = editUri.getText().toString().trim();
if (url.length() > 0) {
addUriAsync(url, forPin);
if (forResult) {
addUriAsync(url, forPin, forResult);
}
}
})
.setOnCancelListener((dialog) -> {
if (forResult) {
setResult(Activity.RESULT_CANCELED);
finish();
}
})
.show();
}
private void addUriAsync(String uri, boolean forPin) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
mHelper.addWebSiteShortcut(uri, forPin);
return null;
}
private void addUriAsync(String uri, boolean forPin, boolean forResult) {
if (forResult) {
new AsyncTask<Void, Void, ShortcutInfo>() {
@Override
protected ShortcutInfo doInBackground(Void... params) {
return mHelper.createShortcutForUrl(uri);
}
@Override
protected void onPostExecute(Void aVoid) {
refreshList();
}
}.execute();
@Override
protected void onPostExecute(ShortcutInfo shortcut) {
setResult(Activity.RESULT_OK,
mShortcutManager.createShortcutResultIntent(shortcut));
finish();
}
}.execute();
} else {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
mHelper.addWebSiteShortcut(uri, forPin);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
refreshList();
}
}.execute();
}
}
private void refreshList() {

View File

@@ -156,8 +156,9 @@ public class ShortcutHelper {
}.execute();
}
private ShortcutInfo createShortcutForUrl(String urlAsString) {
public ShortcutInfo createShortcutForUrl(String urlAsString) {
Log.i(TAG, "createShortcutForUrl: " + urlAsString);
urlAsString = normalizeUrl(urlAsString);
final ShortcutInfo.Builder b = new ShortcutInfo.Builder(mContext, urlAsString);
@@ -202,8 +203,7 @@ public class ShortcutHelper {
}
public void addWebSiteShortcut(String urlAsString, boolean forPin) {
final String uriFinal = urlAsString;
final ShortcutInfo shortcut = createShortcutForUrl(normalizeUrl(uriFinal));
final ShortcutInfo shortcut = createShortcutForUrl(urlAsString);
if (forPin) {
callShortcutManager(() -> mShortcutManager.requestPinShortcut(