blob: f2b3e6ee04c77ec734c1b2464396bb983d0921c9 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.printspooler.ui;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ListActivity;
import android.app.LoaderManager;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.Loader;
import android.database.DataSetObserver;
import android.net.Uri;
import android.os.Bundle;
import android.print.PrintManager;
import android.print.PrintServicesLoader;
import android.printservice.PrintServiceInfo;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.printspooler.R;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* This is an activity for adding a printer or. It consists of a list fed from three adapters:
* <ul>
* <li>{@link #mEnabledServicesAdapter} for all enabled services. If a service has an {@link
* PrintServiceInfo#getAddPrintersActivityName() add printer activity} this is started
* when the item is clicked.</li>
* <li>{@link #mDisabledServicesAdapter} for all disabled services. Once clicked the settings page
* for this service is opened.</li>
* <li>{@link RecommendedServicesAdapter} for a link to all services. If this item is clicked
* the market app is opened to show all print services.</li>
* </ul>
*/
public class AddPrinterActivity extends ListActivity implements
LoaderManager.LoaderCallbacks<List<PrintServiceInfo>>,
AdapterView.OnItemClickListener {
private static final String LOG_TAG = "AddPrinterActivity";
/** Ids for the loaders */
private static final int LOADER_ID_ENABLED_SERVICES = 1;
private static final int LOADER_ID_DISABLED_SERVICES = 2;
/**
* The enabled services list. This is filled from the {@link #LOADER_ID_ENABLED_SERVICES}
* loader in {@link #onLoadFinished}.
*/
private EnabledServicesAdapter mEnabledServicesAdapter;
/**
* The disabled services list. This is filled from the {@link #LOADER_ID_DISABLED_SERVICES}
* loader in {@link #onLoadFinished}.
*/
private DisabledServicesAdapter mDisabledServicesAdapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.add_printer_activity);
mEnabledServicesAdapter = new EnabledServicesAdapter();
mDisabledServicesAdapter = new DisabledServicesAdapter();
ArrayList<ActionAdapter> adapterList = new ArrayList<>(3);
adapterList.add(mEnabledServicesAdapter);
adapterList.add(new RecommendedServicesAdapter());
adapterList.add(mDisabledServicesAdapter);
setListAdapter(new CombinedAdapter(adapterList));
getListView().setOnItemClickListener(this);
getLoaderManager().initLoader(LOADER_ID_ENABLED_SERVICES, null, this);
getLoaderManager().initLoader(LOADER_ID_DISABLED_SERVICES, null, this);
// TODO: Load recommended services
}
@Override
public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) {
switch (id) {
case LOADER_ID_ENABLED_SERVICES:
return new PrintServicesLoader(
(PrintManager) getSystemService(Context.PRINT_SERVICE), this,
PrintManager.ENABLED_SERVICES);
case LOADER_ID_DISABLED_SERVICES:
return new PrintServicesLoader(
(PrintManager) getSystemService(Context.PRINT_SERVICE), this,
PrintManager.DISABLED_SERVICES);
// TODO: Load recommended services
default:
// not reached
return null;
}
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
((ActionAdapter) getListAdapter()).performAction(position);
}
@Override
public void onLoadFinished(Loader<List<PrintServiceInfo>> loader,
List<PrintServiceInfo> data) {
switch (loader.getId()) {
case LOADER_ID_ENABLED_SERVICES:
mEnabledServicesAdapter.updateData(data);
break;
case LOADER_ID_DISABLED_SERVICES:
mDisabledServicesAdapter.updateData(data);
break;
// TODO: Load recommended services
default:
// not reached
}
}
@Override
public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) {
if (!isFinishing()) {
switch (loader.getId()) {
case LOADER_ID_ENABLED_SERVICES:
mEnabledServicesAdapter.updateData(null);
break;
case LOADER_ID_DISABLED_SERVICES:
mDisabledServicesAdapter.updateData(null);
break;
// TODO: Reset recommended services
default:
// not reached
}
}
}
/**
* Marks an adapter that can can perform an action for a position in it's list.
*/
private abstract class ActionAdapter extends BaseAdapter {
/**
* Perform the action for a position in the list.
*
* @param position The position of the item
*/
abstract void performAction(@IntRange(from = 0) int position);
@Override
public boolean areAllItemsEnabled() {
return false;
}
}
/**
* An adapter presenting multiple sub adapters as a single combined adapter.
*/
private class CombinedAdapter extends ActionAdapter {
/** The adapters to combine */
private final @NonNull ArrayList<ActionAdapter> mAdapters;
/**
* Create a combined adapter.
*
* @param adapters the list of adapters to combine
*/
CombinedAdapter(@NonNull ArrayList<ActionAdapter> adapters) {
mAdapters = adapters;
final int numAdapters = mAdapters.size();
for (int i = 0; i < numAdapters; i++) {
mAdapters.get(i).registerDataSetObserver(new DataSetObserver() {
@Override
public void onChanged() {
notifyDataSetChanged();
}
@Override
public void onInvalidated() {
notifyDataSetChanged();
}
});
}
}
@Override
public int getCount() {
int totalCount = 0;
final int numAdapters = mAdapters.size();
for (int i = 0; i < numAdapters; i++) {
totalCount += mAdapters.get(i).getCount();
}
return totalCount;
}
/**
* Find the sub adapter and the position in the sub-adapter the position in the combined
* adapter refers to.
*
* @param position The position in the combined adapter
*
* @return The pair of adapter and position in sub adapter
*/
private @NonNull Pair<ActionAdapter, Integer> getSubAdapter(int position) {
final int numAdapters = mAdapters.size();
for (int i = 0; i < numAdapters; i++) {
ActionAdapter adapter = mAdapters.get(i);
if (position < adapter.getCount()) {
return new Pair<>(adapter, position);
} else {
position -= adapter.getCount();
}
}
throw new IllegalArgumentException("Invalid position");
}
@Override
public int getItemViewType(int position) {
int numLowerViewTypes = 0;
final int numAdapters = mAdapters.size();
for (int i = 0; i < numAdapters; i++) {
Adapter adapter = mAdapters.get(i);
if (position < adapter.getCount()) {
return numLowerViewTypes + adapter.getItemViewType(position);
} else {
numLowerViewTypes += adapter.getViewTypeCount();
position -= adapter.getCount();
}
}
throw new IllegalArgumentException("Invalid position");
}
@Override
public int getViewTypeCount() {
int totalViewCount = 0;
final int numAdapters = mAdapters.size();
for (int i = 0; i < numAdapters; i++) {
totalViewCount += mAdapters.get(i).getViewTypeCount();
}
return totalViewCount;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Pair<ActionAdapter, Integer> realPosition = getSubAdapter(position);
return realPosition.first.getView(realPosition.second, convertView, parent);
}
@Override
public Object getItem(int position) {
Pair<ActionAdapter, Integer> realPosition = getSubAdapter(position);
return realPosition.first.getItem(realPosition.second);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public boolean isEnabled(int position) {
Pair<ActionAdapter, Integer> realPosition = getSubAdapter(position);
return realPosition.first.isEnabled(realPosition.second);
}
@Override
public void performAction(@IntRange(from = 0) int position) {
Pair<ActionAdapter, Integer> realPosition = getSubAdapter(position);
realPosition.first.performAction(realPosition.second);
}
}
/**
* Superclass for all adapters that just display a list of {@link PrintServiceInfo}.
*/
private abstract class PrintServiceInfoAdapter extends ActionAdapter {
/**
* Raw data of the list.
*
* @see #updateData(List)
*/
private @NonNull List<PrintServiceInfo> mServices;
/**
* Create a new adapter.
*/
PrintServiceInfoAdapter() {
mServices = Collections.emptyList();
}
/**
* Update the data.
*
* @param services The new raw data.
*/
void updateData(@Nullable List<PrintServiceInfo> services) {
if (services == null || services.isEmpty()) {
mServices = Collections.emptyList();
} else {
mServices = services;
}
notifyDataSetChanged();
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(int position) {
if (position == 0) {
return 0;
} else {
return 1;
}
}
@Override
public int getCount() {
if (mServices.isEmpty()) {
return 0;
} else {
return mServices.size() + 1;
}
}
@Override
public Object getItem(int position) {
if (position == 0) {
return null;
} else {
return mServices.get(position - 1);
}
}
@Override
public boolean isEnabled(int position) {
return position != 0;
}
@Override
public long getItemId(int position) {
return position;
}
}
/**
* Adapter for the enabled services.
*/
private class EnabledServicesAdapter extends PrintServiceInfoAdapter {
@Override
public void performAction(@IntRange(from = 0) int position) {
PrintServiceInfo service = (PrintServiceInfo) getItem(position);
String addPrinterActivityName = service.getAddPrintersActivityName();
if (!TextUtils.isEmpty(addPrinterActivityName)) {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setComponent(new ComponentName(service.getComponentName().getPackageName(),
addPrinterActivityName));
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
Log.e(LOG_TAG, "Cannot start add printers activity", e);
}
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (position == 0) {
if (convertView == null) {
convertView = getLayoutInflater().inflate(R.layout.add_printer_list_header,
parent, false);
}
((TextView) convertView.findViewById(R.id.text))
.setText(R.string.enabled_services_title);
return convertView;
}
if (convertView == null) {
convertView = getLayoutInflater().inflate(R.layout.enabled_print_services_list_item,
parent, false);
}
PrintServiceInfo service = (PrintServiceInfo) getItem(position);
TextView title = (TextView) convertView.findViewById(R.id.title);
ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
TextView subtitle = (TextView) convertView.findViewById(R.id.subtitle);
title.setText(service.getResolveInfo().loadLabel(getPackageManager()));
icon.setImageDrawable(service.getResolveInfo().loadIcon(getPackageManager()));
if (TextUtils.isEmpty(service.getAddPrintersActivityName())) {
subtitle.setText(getString(R.string.cannot_add_printer));
} else {
subtitle.setText(getString(R.string.select_to_add_printers));
}
return convertView;
}
}
/**
* Adapter for the disabled services.
*/
private class DisabledServicesAdapter extends PrintServiceInfoAdapter {
@Override
public void performAction(@IntRange(from = 0) int position) {
((PrintManager) getSystemService(Context.PRINT_SERVICE)).setPrintServiceEnabled(
((PrintServiceInfo) getItem(position)).getComponentName(), true);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (position == 0) {
if (convertView == null) {
convertView = getLayoutInflater().inflate(R.layout.add_printer_list_header,
parent, false);
}
((TextView) convertView.findViewById(R.id.text))
.setText(R.string.disabled_services_title);
return convertView;
}
if (convertView == null) {
convertView = getLayoutInflater().inflate(
R.layout.disabled_print_services_list_item, parent, false);
}
PrintServiceInfo service = (PrintServiceInfo) getItem(position);
TextView title = (TextView) convertView.findViewById(R.id.title);
ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
title.setText(service.getResolveInfo().loadLabel(getPackageManager()));
icon.setImageDrawable(service.getResolveInfo().loadIcon(getPackageManager()));
return convertView;
}
}
/**
* Adapter for the recommended services.
*/
private class RecommendedServicesAdapter extends ActionAdapter {
@Override
public int getCount() {
return 2;
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(int position) {
if (position == 0) {
return 0;
} else {
return 1;
}
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (position == 0) {
if (convertView == null) {
convertView = getLayoutInflater().inflate(R.layout.add_printer_list_header,
parent, false);
}
((TextView) convertView.findViewById(R.id.text))
.setText(R.string.recommended_services_title);
return convertView;
}
if (convertView == null) {
convertView = getLayoutInflater().inflate(R.layout.all_print_services_list_item,
parent, false);
}
return convertView;
}
@Override
public boolean isEnabled(int position) {
return position != 0;
}
@Override
public void performAction(@IntRange(from = 0) int position) {
String searchUri = Settings.Secure
.getString(getContentResolver(), Settings.Secure.PRINT_SERVICE_SEARCH_URI);
if (searchUri != null) {
try {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri)));
} catch (ActivityNotFoundException e) {
Log.e(LOG_TAG, "Cannot start market", e);
}
}
}
}
}