blob: ffd173c7858d4fd62af903efbba38ce6266b0399 [file] [log] [blame]
/*
* Copyright (C) 2016 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.android.intentresolver;
import static android.content.Context.ACTIVITY_SERVICE;
import static com.android.intentresolver.ResolverListAdapter.ResolveInfoPresentationGetter;
import static java.util.stream.Collectors.toList;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.intentresolver.chooser.DisplayResolveInfo;
import com.android.internal.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* Shows a dialog with actions to take on a chooser target.
*/
public class ChooserTargetActionsDialogFragment extends DialogFragment
implements DialogInterface.OnClickListener {
protected ArrayList<DisplayResolveInfo> mTargetInfos = new ArrayList<>();
protected UserHandle mUserHandle;
protected String mShortcutId;
protected String mShortcutTitle;
protected boolean mIsShortcutPinned;
protected IntentFilter mIntentFilter;
public static final String USER_HANDLE_KEY = "user_handle";
public static final String TARGET_INFOS_KEY = "target_infos";
public static final String SHORTCUT_ID_KEY = "shortcut_id";
public static final String SHORTCUT_TITLE_KEY = "shortcut_title";
public static final String IS_SHORTCUT_PINNED_KEY = "is_shortcut_pinned";
public static final String INTENT_FILTER_KEY = "intent_filter";
public ChooserTargetActionsDialogFragment() {}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
setStateFromBundle(savedInstanceState);
} else {
setStateFromBundle(getArguments());
}
}
void setStateFromBundle(Bundle b) {
mTargetInfos = (ArrayList<DisplayResolveInfo>) b.get(TARGET_INFOS_KEY);
mUserHandle = (UserHandle) b.get(USER_HANDLE_KEY);
mShortcutId = b.getString(SHORTCUT_ID_KEY);
mShortcutTitle = b.getString(SHORTCUT_TITLE_KEY);
mIsShortcutPinned = b.getBoolean(IS_SHORTCUT_PINNED_KEY);
mIntentFilter = (IntentFilter) b.get(INTENT_FILTER_KEY);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(ChooserTargetActionsDialogFragment.USER_HANDLE_KEY,
mUserHandle);
outState.putParcelableArrayList(ChooserTargetActionsDialogFragment.TARGET_INFOS_KEY,
mTargetInfos);
outState.putString(ChooserTargetActionsDialogFragment.SHORTCUT_ID_KEY, mShortcutId);
outState.putBoolean(ChooserTargetActionsDialogFragment.IS_SHORTCUT_PINNED_KEY,
mIsShortcutPinned);
outState.putString(ChooserTargetActionsDialogFragment.SHORTCUT_TITLE_KEY, mShortcutTitle);
outState.putParcelable(ChooserTargetActionsDialogFragment.INTENT_FILTER_KEY, mIntentFilter);
}
/**
* Recreate the layout from scratch to match new Sharesheet redlines
*/
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
Bundle savedInstanceState) {
if (savedInstanceState != null) {
setStateFromBundle(savedInstanceState);
} else {
setStateFromBundle(getArguments());
}
// Make the background transparent to show dialog rounding
Optional.of(getDialog()).map(Dialog::getWindow)
.ifPresent(window -> {
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
});
// Fetch UI details from target info
List<Pair<Drawable, CharSequence>> items = mTargetInfos.stream().map(dri -> {
return new Pair<>(getItemIcon(dri), getItemLabel(dri));
}).collect(toList());
View v = inflater.inflate(R.layout.chooser_dialog, container, false);
TextView title = v.findViewById(com.android.internal.R.id.title);
ImageView icon = v.findViewById(com.android.internal.R.id.icon);
RecyclerView rv = v.findViewById(com.android.internal.R.id.listContainer);
final ResolveInfoPresentationGetter pg = getProvidingAppPresentationGetter();
title.setText(isShortcutTarget() ? mShortcutTitle : pg.getLabel());
icon.setImageDrawable(pg.getIcon(mUserHandle));
rv.setAdapter(new VHAdapter(items));
return v;
}
class VHAdapter extends RecyclerView.Adapter<VH> {
List<Pair<Drawable, CharSequence>> mItems;
VHAdapter(List<Pair<Drawable, CharSequence>> items) {
mItems = items;
}
@NonNull
@Override
public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new VH(LayoutInflater.from(parent.getContext()).inflate(
R.layout.chooser_dialog_item, parent, false));
}
@Override
public void onBindViewHolder(@NonNull VH holder, int position) {
holder.bind(mItems.get(position), position);
}
@Override
public int getItemCount() {
return mItems.size();
}
}
class VH extends RecyclerView.ViewHolder {
TextView mLabel;
ImageView mIcon;
VH(@NonNull View itemView) {
super(itemView);
mLabel = itemView.findViewById(com.android.internal.R.id.text);
mIcon = itemView.findViewById(com.android.internal.R.id.icon);
}
public void bind(Pair<Drawable, CharSequence> item, int position) {
mLabel.setText(item.second);
if (item.first == null) {
mIcon.setVisibility(View.GONE);
} else {
mIcon.setVisibility(View.VISIBLE);
mIcon.setImageDrawable(item.first);
}
itemView.setOnClickListener(v -> onClick(getDialog(), position));
}
}
@Override
public void onClick(DialogInterface dialog, int which) {
if (isShortcutTarget()) {
toggleShortcutPinned(mTargetInfos.get(which).getResolvedComponentName());
} else {
pinComponent(mTargetInfos.get(which).getResolvedComponentName());
}
((ChooserActivity) getActivity()).handlePackagesChanged();
dismiss();
}
private void toggleShortcutPinned(ComponentName name) {
if (mIntentFilter == null) {
return;
}
// Fetch existing pinned shortcuts of the given package.
List<String> pinnedShortcuts = getPinnedShortcutsFromPackageAsUser(getContext(),
mUserHandle, mIntentFilter, name.getPackageName());
// If the shortcut has already been pinned, unpin it; otherwise, pin it.
if (mIsShortcutPinned) {
pinnedShortcuts.remove(mShortcutId);
} else {
pinnedShortcuts.add(mShortcutId);
}
// Update pinned shortcut list in ShortcutService via LauncherApps
getContext().getSystemService(LauncherApps.class).pinShortcuts(
name.getPackageName(), pinnedShortcuts, mUserHandle);
}
private static List<String> getPinnedShortcutsFromPackageAsUser(Context context,
UserHandle user, IntentFilter filter, String packageName) {
Context contextAsUser = context.createContextAsUser(user, 0 /* flags */);
List<ShortcutManager.ShareShortcutInfo> targets = contextAsUser.getSystemService(
ShortcutManager.class).getShareTargets(filter);
return targets.stream()
.map(ShortcutManager.ShareShortcutInfo::getShortcutInfo)
.filter(s -> s.isPinned() && s.getPackage().equals(packageName))
.map(ShortcutInfo::getId)
.collect(Collectors.toList());
}
private void pinComponent(ComponentName name) {
SharedPreferences sp = ChooserActivity.getPinnedSharedPrefs(getContext());
final String key = name.flattenToString();
boolean currentVal = sp.getBoolean(name.flattenToString(), false);
if (currentVal) {
sp.edit().remove(key).apply();
} else {
sp.edit().putBoolean(key, true).apply();
}
}
private Drawable getPinIcon(boolean isPinned) {
return isPinned
? getContext().getDrawable(com.android.internal.R.drawable.ic_close)
: getContext().getDrawable(R.drawable.ic_chooser_pin_dialog);
}
private CharSequence getPinLabel(boolean isPinned, CharSequence targetLabel) {
return isPinned
? getResources().getString(R.string.unpin_specific_target, targetLabel)
: getResources().getString(R.string.pin_specific_target, targetLabel);
}
@NonNull
protected CharSequence getItemLabel(DisplayResolveInfo dri) {
final PackageManager pm = getContext().getPackageManager();
return getPinLabel(isPinned(dri),
isShortcutTarget() ? mShortcutTitle : dri.getResolveInfo().loadLabel(pm));
}
@Nullable
protected Drawable getItemIcon(DisplayResolveInfo dri) {
return getPinIcon(isPinned(dri));
}
private ResolveInfoPresentationGetter getProvidingAppPresentationGetter() {
final ActivityManager am = (ActivityManager) getContext()
.getSystemService(ACTIVITY_SERVICE);
final int iconDpi = am.getLauncherLargeIconDensity();
// Use the matching application icon and label for the title, any TargetInfo will do
return new ResolveInfoPresentationGetter(getContext(), iconDpi,
mTargetInfos.get(0).getResolveInfo());
}
private boolean isPinned(DisplayResolveInfo dri) {
return isShortcutTarget() ? mIsShortcutPinned : dri.isPinned();
}
private boolean isShortcutTarget() {
return mShortcutId != null;
}
}