blob: f2b3e6ee04c77ec734c1b2464396bb983d0921c9 [file] [log] [blame]
Philip P. Moltmann66c96592016-02-24 11:32:43 -08001/*
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
17package com.android.printspooler.ui;
18
19import android.annotation.IntRange;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.app.ListActivity;
23import android.app.LoaderManager;
24import android.content.ActivityNotFoundException;
25import android.content.ComponentName;
26import android.content.Context;
27import android.content.Intent;
28import android.content.Loader;
29import android.database.DataSetObserver;
30import android.net.Uri;
31import android.os.Bundle;
32import android.print.PrintManager;
33import android.print.PrintServicesLoader;
34import android.printservice.PrintServiceInfo;
35import android.provider.Settings;
36import android.text.TextUtils;
37import android.util.Log;
38import android.util.Pair;
39import android.view.View;
40import android.view.ViewGroup;
41import android.widget.Adapter;
42import android.widget.AdapterView;
43import android.widget.BaseAdapter;
44import android.widget.ImageView;
45import android.widget.TextView;
46import com.android.printspooler.R;
47
48import java.util.ArrayList;
49import java.util.Collections;
50import java.util.List;
51
52/**
53 * This is an activity for adding a printer or. It consists of a list fed from three adapters:
54 * <ul>
55 * <li>{@link #mEnabledServicesAdapter} for all enabled services. If a service has an {@link
56 * PrintServiceInfo#getAddPrintersActivityName() add printer activity} this is started
57 * when the item is clicked.</li>
58 * <li>{@link #mDisabledServicesAdapter} for all disabled services. Once clicked the settings page
59 * for this service is opened.</li>
60 * <li>{@link RecommendedServicesAdapter} for a link to all services. If this item is clicked
61 * the market app is opened to show all print services.</li>
62 * </ul>
63 */
64public class AddPrinterActivity extends ListActivity implements
65 LoaderManager.LoaderCallbacks<List<PrintServiceInfo>>,
66 AdapterView.OnItemClickListener {
67 private static final String LOG_TAG = "AddPrinterActivity";
68
69 /** Ids for the loaders */
70 private static final int LOADER_ID_ENABLED_SERVICES = 1;
71 private static final int LOADER_ID_DISABLED_SERVICES = 2;
72
73 /**
74 * The enabled services list. This is filled from the {@link #LOADER_ID_ENABLED_SERVICES}
75 * loader in {@link #onLoadFinished}.
76 */
77 private EnabledServicesAdapter mEnabledServicesAdapter;
78
79 /**
80 * The disabled services list. This is filled from the {@link #LOADER_ID_DISABLED_SERVICES}
81 * loader in {@link #onLoadFinished}.
82 */
83 private DisabledServicesAdapter mDisabledServicesAdapter;
84
85 @Override
86 protected void onCreate(@Nullable Bundle savedInstanceState) {
87 super.onCreate(savedInstanceState);
88
89 setContentView(R.layout.add_printer_activity);
90
91 mEnabledServicesAdapter = new EnabledServicesAdapter();
92 mDisabledServicesAdapter = new DisabledServicesAdapter();
93
94 ArrayList<ActionAdapter> adapterList = new ArrayList<>(3);
95 adapterList.add(mEnabledServicesAdapter);
96 adapterList.add(new RecommendedServicesAdapter());
97 adapterList.add(mDisabledServicesAdapter);
98
99 setListAdapter(new CombinedAdapter(adapterList));
100
101 getListView().setOnItemClickListener(this);
102
103 getLoaderManager().initLoader(LOADER_ID_ENABLED_SERVICES, null, this);
104 getLoaderManager().initLoader(LOADER_ID_DISABLED_SERVICES, null, this);
105 // TODO: Load recommended services
106 }
107
108 @Override
109 public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) {
110 switch (id) {
111 case LOADER_ID_ENABLED_SERVICES:
112 return new PrintServicesLoader(
113 (PrintManager) getSystemService(Context.PRINT_SERVICE), this,
114 PrintManager.ENABLED_SERVICES);
115 case LOADER_ID_DISABLED_SERVICES:
116 return new PrintServicesLoader(
117 (PrintManager) getSystemService(Context.PRINT_SERVICE), this,
118 PrintManager.DISABLED_SERVICES);
119 // TODO: Load recommended services
120 default:
121 // not reached
122 return null;
123 }
124 }
125
126 @Override
127 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
128 ((ActionAdapter) getListAdapter()).performAction(position);
129 }
130
131 @Override
132 public void onLoadFinished(Loader<List<PrintServiceInfo>> loader,
133 List<PrintServiceInfo> data) {
134 switch (loader.getId()) {
135 case LOADER_ID_ENABLED_SERVICES:
136 mEnabledServicesAdapter.updateData(data);
137 break;
138 case LOADER_ID_DISABLED_SERVICES:
139 mDisabledServicesAdapter.updateData(data);
140 break;
141 // TODO: Load recommended services
142 default:
143 // not reached
144 }
145 }
146
147 @Override
148 public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) {
149 if (!isFinishing()) {
150 switch (loader.getId()) {
151 case LOADER_ID_ENABLED_SERVICES:
152 mEnabledServicesAdapter.updateData(null);
153 break;
154 case LOADER_ID_DISABLED_SERVICES:
155 mDisabledServicesAdapter.updateData(null);
156 break;
157 // TODO: Reset recommended services
158 default:
159 // not reached
160 }
161 }
162 }
163
164 /**
165 * Marks an adapter that can can perform an action for a position in it's list.
166 */
167 private abstract class ActionAdapter extends BaseAdapter {
168 /**
169 * Perform the action for a position in the list.
170 *
171 * @param position The position of the item
172 */
173 abstract void performAction(@IntRange(from = 0) int position);
174
175 @Override
176 public boolean areAllItemsEnabled() {
177 return false;
178 }
179 }
180
181 /**
182 * An adapter presenting multiple sub adapters as a single combined adapter.
183 */
184 private class CombinedAdapter extends ActionAdapter {
185 /** The adapters to combine */
186 private final @NonNull ArrayList<ActionAdapter> mAdapters;
187
188 /**
189 * Create a combined adapter.
190 *
191 * @param adapters the list of adapters to combine
192 */
193 CombinedAdapter(@NonNull ArrayList<ActionAdapter> adapters) {
194 mAdapters = adapters;
195
196 final int numAdapters = mAdapters.size();
197 for (int i = 0; i < numAdapters; i++) {
198 mAdapters.get(i).registerDataSetObserver(new DataSetObserver() {
199 @Override
200 public void onChanged() {
201 notifyDataSetChanged();
202 }
203
204 @Override
205 public void onInvalidated() {
206 notifyDataSetChanged();
207 }
208 });
209 }
210 }
211
212 @Override
213 public int getCount() {
214 int totalCount = 0;
215
216 final int numAdapters = mAdapters.size();
217 for (int i = 0; i < numAdapters; i++) {
218 totalCount += mAdapters.get(i).getCount();
219 }
220
221 return totalCount;
222 }
223
224 /**
225 * Find the sub adapter and the position in the sub-adapter the position in the combined
226 * adapter refers to.
227 *
228 * @param position The position in the combined adapter
229 *
230 * @return The pair of adapter and position in sub adapter
231 */
232 private @NonNull Pair<ActionAdapter, Integer> getSubAdapter(int position) {
233 final int numAdapters = mAdapters.size();
234 for (int i = 0; i < numAdapters; i++) {
235 ActionAdapter adapter = mAdapters.get(i);
236
237 if (position < adapter.getCount()) {
238 return new Pair<>(adapter, position);
239 } else {
240 position -= adapter.getCount();
241 }
242 }
243
244 throw new IllegalArgumentException("Invalid position");
245 }
246
247 @Override
248 public int getItemViewType(int position) {
249 int numLowerViewTypes = 0;
250
251 final int numAdapters = mAdapters.size();
252 for (int i = 0; i < numAdapters; i++) {
253 Adapter adapter = mAdapters.get(i);
254
255 if (position < adapter.getCount()) {
256 return numLowerViewTypes + adapter.getItemViewType(position);
257 } else {
258 numLowerViewTypes += adapter.getViewTypeCount();
259 position -= adapter.getCount();
260 }
261 }
262
263 throw new IllegalArgumentException("Invalid position");
264 }
265
266 @Override
267 public int getViewTypeCount() {
268 int totalViewCount = 0;
269
270 final int numAdapters = mAdapters.size();
271 for (int i = 0; i < numAdapters; i++) {
272 totalViewCount += mAdapters.get(i).getViewTypeCount();
273 }
274
275 return totalViewCount;
276 }
277
278 @Override
279 public View getView(int position, View convertView, ViewGroup parent) {
280 Pair<ActionAdapter, Integer> realPosition = getSubAdapter(position);
281
282 return realPosition.first.getView(realPosition.second, convertView, parent);
283 }
284
285 @Override
286 public Object getItem(int position) {
287 Pair<ActionAdapter, Integer> realPosition = getSubAdapter(position);
288
289 return realPosition.first.getItem(realPosition.second);
290 }
291
292 @Override
293 public long getItemId(int position) {
294 return position;
295 }
296
297 @Override
298 public boolean isEnabled(int position) {
299 Pair<ActionAdapter, Integer> realPosition = getSubAdapter(position);
300
301 return realPosition.first.isEnabled(realPosition.second);
302 }
303
304 @Override
305 public void performAction(@IntRange(from = 0) int position) {
306 Pair<ActionAdapter, Integer> realPosition = getSubAdapter(position);
307
308 realPosition.first.performAction(realPosition.second);
309 }
310 }
311
312 /**
313 * Superclass for all adapters that just display a list of {@link PrintServiceInfo}.
314 */
315 private abstract class PrintServiceInfoAdapter extends ActionAdapter {
316 /**
317 * Raw data of the list.
318 *
319 * @see #updateData(List)
320 */
321 private @NonNull List<PrintServiceInfo> mServices;
322
323 /**
324 * Create a new adapter.
325 */
326 PrintServiceInfoAdapter() {
327 mServices = Collections.emptyList();
328 }
329
330 /**
331 * Update the data.
332 *
333 * @param services The new raw data.
334 */
335 void updateData(@Nullable List<PrintServiceInfo> services) {
336 if (services == null || services.isEmpty()) {
337 mServices = Collections.emptyList();
338 } else {
339 mServices = services;
340 }
341
342 notifyDataSetChanged();
343 }
344
345 @Override
346 public int getViewTypeCount() {
347 return 2;
348 }
349
350 @Override
351 public int getItemViewType(int position) {
352 if (position == 0) {
353 return 0;
354 } else {
355 return 1;
356 }
357 }
358
359 @Override
360 public int getCount() {
361 if (mServices.isEmpty()) {
362 return 0;
363 } else {
364 return mServices.size() + 1;
365 }
366 }
367
368 @Override
369 public Object getItem(int position) {
370 if (position == 0) {
371 return null;
372 } else {
373 return mServices.get(position - 1);
374 }
375 }
376
377 @Override
378 public boolean isEnabled(int position) {
379 return position != 0;
380 }
381
382 @Override
383 public long getItemId(int position) {
384 return position;
385 }
386 }
387
388 /**
389 * Adapter for the enabled services.
390 */
391 private class EnabledServicesAdapter extends PrintServiceInfoAdapter {
392 @Override
393 public void performAction(@IntRange(from = 0) int position) {
394 PrintServiceInfo service = (PrintServiceInfo) getItem(position);
395 String addPrinterActivityName = service.getAddPrintersActivityName();
396
397 if (!TextUtils.isEmpty(addPrinterActivityName)) {
398 Intent intent = new Intent(Intent.ACTION_MAIN);
399 intent.setComponent(new ComponentName(service.getComponentName().getPackageName(),
400 addPrinterActivityName));
401
402 try {
403 startActivity(intent);
404 } catch (ActivityNotFoundException e) {
405 Log.e(LOG_TAG, "Cannot start add printers activity", e);
406 }
407 }
408 }
409
410 @Override
411 public View getView(int position, View convertView, ViewGroup parent) {
412 if (position == 0) {
413 if (convertView == null) {
414 convertView = getLayoutInflater().inflate(R.layout.add_printer_list_header,
415 parent, false);
416 }
417
418 ((TextView) convertView.findViewById(R.id.text))
419 .setText(R.string.enabled_services_title);
420
421 return convertView;
422 }
423
424 if (convertView == null) {
425 convertView = getLayoutInflater().inflate(R.layout.enabled_print_services_list_item,
426 parent, false);
427 }
428
429 PrintServiceInfo service = (PrintServiceInfo) getItem(position);
430
431 TextView title = (TextView) convertView.findViewById(R.id.title);
432 ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
433 TextView subtitle = (TextView) convertView.findViewById(R.id.subtitle);
434
435 title.setText(service.getResolveInfo().loadLabel(getPackageManager()));
436 icon.setImageDrawable(service.getResolveInfo().loadIcon(getPackageManager()));
437
438 if (TextUtils.isEmpty(service.getAddPrintersActivityName())) {
439 subtitle.setText(getString(R.string.cannot_add_printer));
440 } else {
441 subtitle.setText(getString(R.string.select_to_add_printers));
442 }
443
444 return convertView;
445 }
446 }
447
448 /**
449 * Adapter for the disabled services.
450 */
451 private class DisabledServicesAdapter extends PrintServiceInfoAdapter {
452 @Override
453 public void performAction(@IntRange(from = 0) int position) {
454 ((PrintManager) getSystemService(Context.PRINT_SERVICE)).setPrintServiceEnabled(
455 ((PrintServiceInfo) getItem(position)).getComponentName(), true);
456 }
457
458 @Override
459 public View getView(int position, View convertView, ViewGroup parent) {
460 if (position == 0) {
461 if (convertView == null) {
462 convertView = getLayoutInflater().inflate(R.layout.add_printer_list_header,
463 parent, false);
464 }
465
466 ((TextView) convertView.findViewById(R.id.text))
467 .setText(R.string.disabled_services_title);
468
469 return convertView;
470 }
471
472 if (convertView == null) {
473 convertView = getLayoutInflater().inflate(
474 R.layout.disabled_print_services_list_item, parent, false);
475 }
476
477 PrintServiceInfo service = (PrintServiceInfo) getItem(position);
478
479 TextView title = (TextView) convertView.findViewById(R.id.title);
480 ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
481
482 title.setText(service.getResolveInfo().loadLabel(getPackageManager()));
483 icon.setImageDrawable(service.getResolveInfo().loadIcon(getPackageManager()));
484
485 return convertView;
486 }
487 }
488
489 /**
490 * Adapter for the recommended services.
491 */
492 private class RecommendedServicesAdapter extends ActionAdapter {
493 @Override
494 public int getCount() {
495 return 2;
496 }
497
498 @Override
499 public int getViewTypeCount() {
500 return 2;
501 }
502
503 @Override
504 public int getItemViewType(int position) {
505 if (position == 0) {
506 return 0;
507 } else {
508 return 1;
509 }
510 }
511
512 @Override
513 public Object getItem(int position) {
514 return null;
515 }
516
517 @Override
518 public long getItemId(int position) {
519 return position;
520 }
521
522 @Override
523 public View getView(int position, View convertView, ViewGroup parent) {
524 if (position == 0) {
525 if (convertView == null) {
526 convertView = getLayoutInflater().inflate(R.layout.add_printer_list_header,
527 parent, false);
528 }
529
530 ((TextView) convertView.findViewById(R.id.text))
531 .setText(R.string.recommended_services_title);
532
533 return convertView;
534 }
535
536 if (convertView == null) {
537 convertView = getLayoutInflater().inflate(R.layout.all_print_services_list_item,
538 parent, false);
539 }
540
541 return convertView;
542 }
543
544 @Override
545 public boolean isEnabled(int position) {
546 return position != 0;
547 }
548
549 @Override
550 public void performAction(@IntRange(from = 0) int position) {
551 String searchUri = Settings.Secure
552 .getString(getContentResolver(), Settings.Secure.PRINT_SERVICE_SEARCH_URI);
553
554 if (searchUri != null) {
555 try {
556 startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri)));
557 } catch (ActivityNotFoundException e) {
558 Log.e(LOG_TAG, "Cannot start market", e);
559 }
560 }
561 }
562 }
563}