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