1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 18 package com.android.internal.app; 19 20 import static android.content.Context.ACTIVITY_SERVICE; 21 22 import static com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter; 23 24 import static java.util.stream.Collectors.toList; 25 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.app.ActivityManager; 29 import android.app.Dialog; 30 import android.app.DialogFragment; 31 import android.content.ComponentName; 32 import android.content.DialogInterface; 33 import android.content.SharedPreferences; 34 import android.content.pm.PackageManager; 35 import android.graphics.Color; 36 import android.graphics.drawable.ColorDrawable; 37 import android.graphics.drawable.Drawable; 38 import android.os.Bundle; 39 import android.os.UserHandle; 40 import android.util.Pair; 41 import android.view.LayoutInflater; 42 import android.view.View; 43 import android.view.ViewGroup; 44 import android.widget.ImageView; 45 import android.widget.TextView; 46 47 import com.android.internal.R; 48 import com.android.internal.app.chooser.DisplayResolveInfo; 49 import com.android.internal.widget.RecyclerView; 50 51 import java.util.ArrayList; 52 import java.util.List; 53 import java.util.Optional; 54 55 /** 56 * Shows a dialog with actions to take on a chooser target. 57 */ 58 public class ChooserTargetActionsDialogFragment extends DialogFragment 59 implements DialogInterface.OnClickListener { 60 61 protected ArrayList<DisplayResolveInfo> mTargetInfos = new ArrayList<>(); 62 protected UserHandle mUserHandle; 63 64 public static final String USER_HANDLE_KEY = "user_handle"; 65 public static final String TARGET_INFOS_KEY = "target_infos"; 66 ChooserTargetActionsDialogFragment()67 public ChooserTargetActionsDialogFragment() {} 68 69 @Override onCreate(Bundle savedInstanceState)70 public void onCreate(Bundle savedInstanceState) { 71 super.onCreate(savedInstanceState); 72 if (savedInstanceState != null) { 73 setStateFromBundle(savedInstanceState); 74 } else { 75 setStateFromBundle(getArguments()); 76 } 77 } 78 setStateFromBundle(Bundle b)79 void setStateFromBundle(Bundle b) { 80 mTargetInfos = (ArrayList<DisplayResolveInfo>) b.get(TARGET_INFOS_KEY); 81 mUserHandle = (UserHandle) b.get(USER_HANDLE_KEY); 82 } 83 84 @Override onSaveInstanceState(Bundle outState)85 public void onSaveInstanceState(Bundle outState) { 86 super.onSaveInstanceState(outState); 87 88 outState.putParcelable(ChooserTargetActionsDialogFragment.USER_HANDLE_KEY, 89 mUserHandle); 90 outState.putParcelableArrayList(ChooserTargetActionsDialogFragment.TARGET_INFOS_KEY, 91 mTargetInfos); 92 } 93 94 /** 95 * Recreate the layout from scratch to match new Sharesheet redlines 96 */ 97 @Override onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState)98 public View onCreateView(LayoutInflater inflater, 99 @Nullable ViewGroup container, 100 Bundle savedInstanceState) { 101 if (savedInstanceState != null) { 102 setStateFromBundle(savedInstanceState); 103 } else { 104 setStateFromBundle(getArguments()); 105 } 106 // Make the background transparent to show dialog rounding 107 Optional.of(getDialog()).map(Dialog::getWindow) 108 .ifPresent(window -> { 109 window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); 110 }); 111 112 // Fetch UI details from target info 113 List<Pair<Drawable, CharSequence>> items = mTargetInfos.stream().map(dri -> { 114 return new Pair<>(getItemIcon(dri), getItemLabel(dri)); 115 }).collect(toList()); 116 117 View v = inflater.inflate(R.layout.chooser_dialog, container, false); 118 119 TextView title = v.findViewById(R.id.title); 120 ImageView icon = v.findViewById(R.id.icon); 121 RecyclerView rv = v.findViewById(R.id.listContainer); 122 123 final ResolveInfoPresentationGetter pg = getProvidingAppPresentationGetter(); 124 title.setText(pg.getLabel()); 125 icon.setImageDrawable(pg.getIcon(mUserHandle)); 126 rv.setAdapter(new VHAdapter(items)); 127 128 return v; 129 } 130 131 class VHAdapter extends RecyclerView.Adapter<VH> { 132 133 List<Pair<Drawable, CharSequence>> mItems; 134 VHAdapter(List<Pair<Drawable, CharSequence>> items)135 VHAdapter(List<Pair<Drawable, CharSequence>> items) { 136 mItems = items; 137 } 138 139 @NonNull 140 @Override onCreateViewHolder(@onNull ViewGroup parent, int viewType)141 public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 142 return new VH(LayoutInflater.from(parent.getContext()).inflate( 143 R.layout.chooser_dialog_item, parent, false)); 144 } 145 146 @Override onBindViewHolder(@onNull VH holder, int position)147 public void onBindViewHolder(@NonNull VH holder, int position) { 148 holder.bind(mItems.get(position), position); 149 } 150 151 @Override getItemCount()152 public int getItemCount() { 153 return mItems.size(); 154 } 155 } 156 157 class VH extends RecyclerView.ViewHolder { 158 TextView mLabel; 159 ImageView mIcon; 160 VH(@onNull View itemView)161 VH(@NonNull View itemView) { 162 super(itemView); 163 mLabel = itemView.findViewById(R.id.text); 164 mIcon = itemView.findViewById(R.id.icon); 165 } 166 bind(Pair<Drawable, CharSequence> item, int position)167 public void bind(Pair<Drawable, CharSequence> item, int position) { 168 mLabel.setText(item.second); 169 170 if (item.first == null) { 171 mIcon.setVisibility(View.GONE); 172 } else { 173 mIcon.setVisibility(View.VISIBLE); 174 mIcon.setImageDrawable(item.first); 175 } 176 177 itemView.setOnClickListener(v -> onClick(getDialog(), position)); 178 } 179 } 180 181 @Override onClick(DialogInterface dialog, int which)182 public void onClick(DialogInterface dialog, int which) { 183 pinComponent(mTargetInfos.get(which).getResolvedComponentName()); 184 ((ChooserActivity) getActivity()).handlePackagesChanged(); 185 dismiss(); 186 } 187 pinComponent(ComponentName name)188 private void pinComponent(ComponentName name) { 189 SharedPreferences sp = ChooserActivity.getPinnedSharedPrefs(getContext()); 190 final String key = name.flattenToString(); 191 boolean currentVal = sp.getBoolean(name.flattenToString(), false); 192 if (currentVal) { 193 sp.edit().remove(key).apply(); 194 } else { 195 sp.edit().putBoolean(key, true).apply(); 196 } 197 } 198 getPinIcon(boolean isPinned)199 private Drawable getPinIcon(boolean isPinned) { 200 return isPinned 201 ? getContext().getDrawable(R.drawable.ic_close) 202 : getContext().getDrawable(R.drawable.ic_chooser_pin_dialog); 203 } 204 getPinLabel(boolean isPinned, CharSequence targetLabel)205 private CharSequence getPinLabel(boolean isPinned, CharSequence targetLabel) { 206 return isPinned 207 ? getResources().getString(R.string.unpin_specific_target, targetLabel) 208 : getResources().getString(R.string.pin_specific_target, targetLabel); 209 } 210 211 @NonNull getItemLabel(DisplayResolveInfo dri)212 protected CharSequence getItemLabel(DisplayResolveInfo dri) { 213 final PackageManager pm = getContext().getPackageManager(); 214 return getPinLabel(dri.isPinned(), dri.getResolveInfo().loadLabel(pm)); 215 } 216 217 @Nullable getItemIcon(DisplayResolveInfo dri)218 protected Drawable getItemIcon(DisplayResolveInfo dri) { 219 return getPinIcon(dri.isPinned()); 220 } 221 getProvidingAppPresentationGetter()222 private ResolveInfoPresentationGetter getProvidingAppPresentationGetter() { 223 final ActivityManager am = (ActivityManager) getContext() 224 .getSystemService(ACTIVITY_SERVICE); 225 final int iconDpi = am.getLauncherLargeIconDensity(); 226 227 // Use the matching application icon and label for the title, any TargetInfo will do 228 return new ResolveInfoPresentationGetter(getContext(), iconDpi, 229 mTargetInfos.get(0).getResolveInfo()); 230 } 231 232 } 233