1 /* 2 * Copyright (C) 2019 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 package com.android.bips.ui; 18 19 import android.content.Intent; 20 import android.content.pm.PackageInfo; 21 import android.content.pm.PackageManager; 22 import android.net.Uri; 23 import android.os.Bundle; 24 import android.print.PrintManager; 25 import android.printservice.recommendation.RecommendationInfo; 26 import android.util.Log; 27 28 import androidx.preference.Preference; 29 import androidx.preference.PreferenceCategory; 30 import androidx.preference.PreferenceFragmentCompat; 31 32 import com.android.bips.R; 33 import com.android.bips.flags.Flags; 34 35 import java.net.InetAddress; 36 import java.text.Collator; 37 import java.util.ArrayList; 38 import java.util.Collections; 39 import java.util.HashMap; 40 import java.util.List; 41 import java.util.Map; 42 43 /** 44 * A fragment allowing the user to review recommended print services and install or enable them. 45 */ 46 public class MoreOptionsFragment extends PreferenceFragmentCompat implements 47 PrintManager.PrintServiceRecommendationsChangeListener { 48 private static final String TAG = MoreOptionsFragment.class.getSimpleName(); 49 private static final boolean DEBUG = false; 50 51 private static final String KEY_RECOMMENDATION_CATEGORY = "recommendation_category"; 52 private static final String KEY_MANAGE = "manage"; 53 private static final String PACKAGE_NAME_VENDING = "com.android.vending"; 54 private static final Collator COLLATOR = Collator.getInstance(); 55 56 private PrintManager mPrintManager; 57 private PackageManager mPackageManager; 58 private PreferenceCategory mRecommendations; 59 private MoreOptionsActivity mActivity; 60 private boolean mHasVending = false; 61 private Map<String, RecommendationItem> mItems = new HashMap<>(); 62 63 @Override onCreatePreferences(Bundle savedInstanceState, String rootKey)64 public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 65 if ((Flags.printerInfoDetails())) { 66 addPreferencesFromResource(R.xml.more_options_prefs_new); 67 } else { 68 addPreferencesFromResource(R.xml.more_options_prefs); 69 } 70 71 mRecommendations = getPreferenceScreen().findPreference( 72 KEY_RECOMMENDATION_CATEGORY); 73 if ((Flags.printerInfoDetails())) { 74 mRecommendations.setIconSpaceReserved(false); 75 } 76 77 getPreferenceScreen().findPreference(KEY_MANAGE) 78 .setOnPreferenceClickListener(preference -> { 79 startActivity(new Intent(android.provider.Settings.ACTION_PRINT_SETTINGS) 80 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 81 return true; 82 }); 83 } 84 85 @Override onStart()86 public void onStart() { 87 super.onStart(); 88 mPackageManager = getContext().getPackageManager(); 89 90 try { 91 // Attempt to get vending package, throws if missing 92 mPackageManager.getPackageInfo(PACKAGE_NAME_VENDING, 0); 93 mHasVending = true; 94 } catch (PackageManager.NameNotFoundException ignored) { 95 } 96 97 mActivity = (MoreOptionsActivity) getActivity(); 98 if ((Flags.printerInfoDetails())) { 99 mActivity.setTitle(mActivity.getResources().getString(R.string.recommendation_link)); 100 } 101 102 mPrintManager = getContext().getSystemService(PrintManager.class); 103 104 // Start listening for recommendation changes 105 mPrintManager.addPrintServiceRecommendationsChangeListener(this, null); 106 onPrintServiceRecommendationsChanged(); 107 } 108 109 @Override onStop()110 public void onStop() { 111 super.onStop(); 112 mPrintManager.removePrintServiceRecommendationsChangeListener(this); 113 } 114 115 @Override onPrintServiceRecommendationsChanged()116 public void onPrintServiceRecommendationsChanged() { 117 List<RecommendationInfo> infos = mPrintManager.getPrintServiceRecommendations(); 118 if (DEBUG) Log.d(TAG, "All recommendations count=" + infos.size()); 119 // Update items list with new recommendations. Recommendations don't go away. 120 for (RecommendationInfo info : infos) { 121 for (InetAddress address : info.getDiscoveredPrinters()) { 122 if (address.equals(mActivity.mPrinterAddress)) { 123 // This recommendation matches so create or update an item for it 124 String packageName = info.getPackageName().toString(); 125 RecommendationItem item = getOrCreateItem(info); 126 try { 127 // If this doesn't throw then the service is installed 128 item.mPrintService = mPackageManager.getPackageInfo(packageName, 0); 129 } catch (PackageManager.NameNotFoundException e) { 130 item.mPrintService = null; 131 } 132 break; 133 } 134 } 135 } 136 137 // Update preferences with ordering 138 List<RecommendationItem> itemList = new ArrayList<>(mItems.values()); 139 Collections.sort(itemList); 140 for (int index = 0; index < itemList.size(); index++) { 141 itemList.get(index).updatePreference(index); 142 } 143 144 if (DEBUG) Log.d(TAG, "For this printer=" + mRecommendations.getPreferenceCount()); 145 146 // Show group if not empty 147 if (mRecommendations.getPreferenceCount() == 0) { 148 getPreferenceScreen().removePreference(mRecommendations); 149 } else { 150 getPreferenceScreen().addPreference(mRecommendations); 151 } 152 } 153 getOrCreateItem(RecommendationInfo recommendationInfo)154 private RecommendationItem getOrCreateItem(RecommendationInfo recommendationInfo) { 155 String packageName = recommendationInfo.getPackageName().toString(); 156 RecommendationItem item = mItems.get(packageName); 157 if (item == null) { 158 item = new RecommendationItem(recommendationInfo); 159 mItems.put(packageName, item); 160 } else { 161 item.mRecommendationInfo = recommendationInfo; 162 } 163 return item; 164 } 165 166 /** An item corresponding to a recommended print service. */ 167 private class RecommendationItem implements Comparable<RecommendationItem> { 168 RecommendationInfo mRecommendationInfo; 169 String mPackageName; 170 Preference mPreference = new Preference(getContext()); 171 /** Present only if the corresponding print service is installed. */ 172 PackageInfo mPrintService; 173 RecommendationItem(RecommendationInfo info)174 RecommendationItem(RecommendationInfo info) { 175 mRecommendationInfo = info; 176 mPackageName = mRecommendationInfo.getPackageName().toString(); 177 } 178 updatePreference(int order)179 void updatePreference(int order) { 180 mPreference.setKey(mPackageName); 181 mPreference.setTitle(mRecommendationInfo.getName()); 182 mPreference.setOrder(order); 183 if (mPrintService != null) { 184 updateEnabler(); 185 if (mRecommendations.findPreference(mPackageName) == null) { 186 mRecommendations.addPreference(mPreference); 187 } 188 } else if (mHasVending) { 189 updateDownloader(); 190 if (mRecommendations.findPreference(mPackageName) == null) { 191 mRecommendations.addPreference(mPreference); 192 } 193 } else { 194 mRecommendations.removePreference(mPreference); 195 } 196 } 197 198 /** Update the preference to allow the user to enable an installed print service. */ updateEnabler()199 private void updateEnabler() { 200 try { 201 // Set icon from application if possible 202 mPreference.setIcon(mPackageManager.getApplicationIcon( 203 mPrintService.packageName)); 204 } catch (PackageManager.NameNotFoundException e) { 205 mPreference.setIcon(null); 206 mPreference.setIconSpaceReserved(true); 207 } 208 mPreference.setSummary(R.string.recommendation_enable_summary); 209 mPreference.setOnPreferenceClickListener(preference -> { 210 // There's no activity to go directly to the print service screen 211 startActivity(new Intent(android.provider.Settings.ACTION_PRINT_SETTINGS) 212 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 213 return true; 214 }); 215 } 216 217 /** Update this preference to reflect a print service that must be downloaded. */ updateDownloader()218 private void updateDownloader() { 219 mPreference.setIcon(R.drawable.ic_download_from_market); 220 mPreference.setSummary(R.string.recommendation_install_summary); 221 mPreference.setOnPreferenceClickListener(preference -> { 222 Uri printServiceUri = Uri.parse("market://details?id=" 223 + mRecommendationInfo.getPackageName()); 224 startActivity(new Intent(Intent.ACTION_VIEW, printServiceUri).setFlags( 225 Intent.FLAG_ACTIVITY_NEW_TASK)); 226 return true; 227 }); 228 } 229 230 @Override compareTo(RecommendationItem other)231 public int compareTo(RecommendationItem other) { 232 // Sort items: 233 // - first by single-vendor (more likely to be manufacturer-specific), 234 // - then alphabetically. 235 if (mRecommendationInfo.recommendsMultiVendorService() 236 != other.mRecommendationInfo.recommendsMultiVendorService()) { 237 return mRecommendationInfo.recommendsMultiVendorService() ? 1 : -1; 238 } 239 240 return COLLATOR.compare(mRecommendationInfo.getName().toString(), 241 other.mRecommendationInfo.getName().toString()); 242 } 243 } 244 } 245