1/*
2 * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
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
17package org.openintents.openpgp.util;
18
19import android.app.AlertDialog.Builder;
20import android.content.Context;
21import android.content.DialogInterface;
22import android.content.Intent;
23import android.content.pm.ResolveInfo;
24import android.content.res.TypedArray;
25import android.graphics.drawable.Drawable;
26import android.net.Uri;
27import android.preference.DialogPreference;
28import android.util.AttributeSet;
29import android.view.View;
30import android.view.ViewGroup;
31import android.widget.ArrayAdapter;
32import android.widget.ListAdapter;
33import android.widget.TextView;
34import org.openintents.openpgp.R;
35
36import java.util.ArrayList;
37import java.util.List;
38
39/**
40 * Does not extend ListPreference, but is very similar to it!
41 * http://grepcode.com/file_/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/preference/ListPreference.java/?v=source
42 */
43public class OpenPgpListPreference extends DialogPreference {
44 private static final String OPENKEYCHAIN_PACKAGE = "org.sufficientlysecure.keychain";
45 private static final String MARKET_INTENT_URI_BASE = "market://details?id=%s";
46 private static final Intent MARKET_INTENT = new Intent(Intent.ACTION_VIEW, Uri.parse(
47 String.format(MARKET_INTENT_URI_BASE, OPENKEYCHAIN_PACKAGE)));
48
49 private ArrayList<OpenPgpProviderEntry> mLegacyList = new ArrayList<OpenPgpProviderEntry>();
50 private ArrayList<OpenPgpProviderEntry> mList = new ArrayList<OpenPgpProviderEntry>();
51
52 private String mSelectedPackage;
53
54 public OpenPgpListPreference(Context context, AttributeSet attrs) {
55 super(context, attrs);
56 }
57
58 public OpenPgpListPreference(Context context) {
59 this(context, null);
60 }
61
62 /**
63 * Public method to add new entries for legacy applications
64 *
65 * @param packageName
66 * @param simpleName
67 * @param icon
68 */
69 public void addLegacyProvider(int position, String packageName, String simpleName, Drawable icon) {
70 mLegacyList.add(position, new OpenPgpProviderEntry(packageName, simpleName, icon));
71 }
72
73 @Override
74 protected void onPrepareDialogBuilder(Builder builder) {
75 mList.clear();
76
77 // add "none"-entry
78 mList.add(0, new OpenPgpProviderEntry("",
79 getContext().getString(R.string.openpgp_list_preference_none),
80 getContext().getResources().getDrawable(R.drawable.ic_action_cancel_launchersize)));
81
82 // add all additional (legacy) providers
83 mList.addAll(mLegacyList);
84
85 // search for OpenPGP providers...
86 ArrayList<OpenPgpProviderEntry> providerList = new ArrayList<OpenPgpProviderEntry>();
87 Intent intent = new Intent(OpenPgpApi.SERVICE_INTENT);
88 List<ResolveInfo> resInfo = getContext().getPackageManager().queryIntentServices(intent, 0);
89 if (!resInfo.isEmpty()) {
90 for (ResolveInfo resolveInfo : resInfo) {
91 if (resolveInfo.serviceInfo == null)
92 continue;
93
94 String packageName = resolveInfo.serviceInfo.packageName;
95 String simpleName = String.valueOf(resolveInfo.serviceInfo.loadLabel(getContext()
96 .getPackageManager()));
97 Drawable icon = resolveInfo.serviceInfo.loadIcon(getContext().getPackageManager());
98
99 providerList.add(new OpenPgpProviderEntry(packageName, simpleName, icon));
100 }
101 }
102
103 if (providerList.isEmpty()) {
104 // add install links if provider list is empty
105 resInfo = getContext().getPackageManager().queryIntentActivities
106 (MARKET_INTENT, 0);
107 for (ResolveInfo resolveInfo : resInfo) {
108 Intent marketIntent = new Intent(MARKET_INTENT);
109 marketIntent.setPackage(resolveInfo.activityInfo.packageName);
110 Drawable icon = resolveInfo.activityInfo.loadIcon(getContext().getPackageManager());
111 String marketName = String.valueOf(resolveInfo.activityInfo.applicationInfo
112 .loadLabel(getContext().getPackageManager()));
113 String simpleName = String.format(getContext().getString(R.string
114 .openpgp_install_openkeychain_via), marketName);
115 mList.add(new OpenPgpProviderEntry(OPENKEYCHAIN_PACKAGE, simpleName,
116 icon, marketIntent));
117 }
118 } else {
119 // add provider
120 mList.addAll(providerList);
121 }
122
123 // Init ArrayAdapter with OpenPGP Providers
124 ListAdapter adapter = new ArrayAdapter<OpenPgpProviderEntry>(getContext(),
125 android.R.layout.select_dialog_singlechoice, android.R.id.text1, mList) {
126 public View getView(int position, View convertView, ViewGroup parent) {
127 // User super class to create the View
128 View v = super.getView(position, convertView, parent);
129 TextView tv = (TextView) v.findViewById(android.R.id.text1);
130
131 // Put the image on the TextView
132 tv.setCompoundDrawablesWithIntrinsicBounds(mList.get(position).icon, null,
133 null, null);
134
135 // Add margin between image and text (support various screen densities)
136 int dp10 = (int) (10 * getContext().getResources().getDisplayMetrics().density + 0.5f);
137 tv.setCompoundDrawablePadding(dp10);
138
139 return v;
140 }
141 };
142
143 builder.setSingleChoiceItems(adapter, getIndexOfProviderList(getValue()),
144 new DialogInterface.OnClickListener() {
145
146 @Override
147 public void onClick(DialogInterface dialog, int which) {
148 OpenPgpProviderEntry entry = mList.get(which);
149
150 if (entry.intent != null) {
151 /*
152 * Intents are called as activity
153 *
154 * Current approach is to assume the user installed the app.
155 * If he does not, the selected package is not valid.
156 *
157 * However applications should always consider this could happen,
158 * as the user might remove the currently used OpenPGP app.
159 */
160 getContext().startActivity(entry.intent);
161 }
162
163 mSelectedPackage = entry.packageName;
164
165 /*
166 * Clicking on an item simulates the positive button click, and dismisses
167 * the dialog.
168 */
169 OpenPgpListPreference.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
170 dialog.dismiss();
171 }
172 });
173
174 /*
175 * The typical interaction for list-based dialogs is to have click-on-an-item dismiss the
176 * dialog instead of the user having to press 'Ok'.
177 */
178 builder.setPositiveButton(null, null);
179 }
180
181 @Override
182 protected void onDialogClosed(boolean positiveResult) {
183 super.onDialogClosed(positiveResult);
184
185 if (positiveResult && (mSelectedPackage != null)) {
186 if (callChangeListener(mSelectedPackage)) {
187 setValue(mSelectedPackage);
188 }
189 }
190 }
191
192 private int getIndexOfProviderList(String packageName) {
193 for (OpenPgpProviderEntry app : mList) {
194 if (app.packageName.equals(packageName)) {
195 return mList.indexOf(app);
196 }
197 }
198
199 return -1;
200 }
201
202 public void setValue(String packageName) {
203 mSelectedPackage = packageName;
204 persistString(packageName);
205 }
206
207 public String getValue() {
208 return mSelectedPackage;
209 }
210
211 public String getEntry() {
212 return getEntryByValue(mSelectedPackage);
213 }
214
215 @Override
216 protected Object onGetDefaultValue(TypedArray a, int index) {
217 return a.getString(index);
218 }
219
220 @Override
221 protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
222 setValue(restoreValue ? getPersistedString(mSelectedPackage) : (String) defaultValue);
223 }
224
225 public String getEntryByValue(String packageName) {
226 for (OpenPgpProviderEntry app : mList) {
227 if (app.packageName.equals(packageName)) {
228 return app.simpleName;
229 }
230 }
231
232 return null;
233 }
234
235 private static class OpenPgpProviderEntry {
236 private String packageName;
237 private String simpleName;
238 private Drawable icon;
239 private Intent intent;
240
241 public OpenPgpProviderEntry(String packageName, String simpleName, Drawable icon) {
242 this.packageName = packageName;
243 this.simpleName = simpleName;
244 this.icon = icon;
245 }
246
247 public OpenPgpProviderEntry(String packageName, String simpleName, Drawable icon, Intent intent) {
248 this(packageName, simpleName, icon);
249 this.intent = intent;
250 }
251
252 @Override
253 public String toString() {
254 return simpleName;
255 }
256 }
257}