1. If a printer is not available it has to disabled and grayed out. 2. Cancle a print job if the app does not provide the requested pages. 3. Fix current printer selection flicker when the print dialog is showing up. Often the current printer is initially set to the user's favorite and then it is changed back to the PDF one. bug:10983508 Change-Id: I8d53eb992cf1c92675ec09f61b2ec272b962fa68
545 lines
21 KiB
Java
545 lines
21 KiB
Java
/*
|
|
* Copyright (C) 2013 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;
|
|
|
|
import android.app.Activity;
|
|
import android.app.AlertDialog;
|
|
import android.app.Dialog;
|
|
import android.app.DialogFragment;
|
|
import android.app.Fragment;
|
|
import android.app.FragmentTransaction;
|
|
import android.app.ListFragment;
|
|
import android.app.LoaderManager;
|
|
import android.content.ActivityNotFoundException;
|
|
import android.content.ComponentName;
|
|
import android.content.Context;
|
|
import android.content.DialogInterface;
|
|
import android.content.Intent;
|
|
import android.content.Loader;
|
|
import android.content.pm.ActivityInfo;
|
|
import android.content.pm.PackageInfo;
|
|
import android.content.pm.PackageManager;
|
|
import android.content.pm.PackageManager.NameNotFoundException;
|
|
import android.content.pm.ResolveInfo;
|
|
import android.content.pm.ServiceInfo;
|
|
import android.database.DataSetObserver;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.net.Uri;
|
|
import android.os.Bundle;
|
|
import android.print.PrintManager;
|
|
import android.print.PrinterId;
|
|
import android.print.PrinterInfo;
|
|
import android.printservice.PrintServiceInfo;
|
|
import android.provider.Settings;
|
|
import android.text.TextUtils;
|
|
import android.util.Log;
|
|
import android.view.Menu;
|
|
import android.view.MenuInflater;
|
|
import android.view.MenuItem;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.widget.ArrayAdapter;
|
|
import android.widget.BaseAdapter;
|
|
import android.widget.Filter;
|
|
import android.widget.Filterable;
|
|
import android.widget.ImageView;
|
|
import android.widget.ListView;
|
|
import android.widget.SearchView;
|
|
import android.widget.TextView;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* This is a fragment for selecting a printer.
|
|
*/
|
|
public final class SelectPrinterFragment extends ListFragment {
|
|
|
|
private static final String LOG_TAG = "SelectPrinterFragment";
|
|
|
|
private static final int LOADER_ID_PRINTERS_LOADER = 1;
|
|
|
|
private static final String FRAGMRNT_TAG_ADD_PRINTER_DIALOG =
|
|
"FRAGMRNT_TAG_ADD_PRINTER_DIALOG";
|
|
|
|
private static final String FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS =
|
|
"FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS";
|
|
|
|
private final ArrayList<PrintServiceInfo> mAddPrinterServices =
|
|
new ArrayList<PrintServiceInfo>();
|
|
|
|
private AnnounceFilterResult mAnnounceFilterResult;
|
|
|
|
public static interface OnPrinterSelectedListener {
|
|
public void onPrinterSelected(PrinterId printerId);
|
|
}
|
|
|
|
@Override
|
|
public void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
setHasOptionsMenu(true);
|
|
}
|
|
|
|
@Override
|
|
public void onActivityCreated(Bundle savedInstanceState) {
|
|
super.onActivityCreated(savedInstanceState);
|
|
final DestinationAdapter adapter = new DestinationAdapter();
|
|
adapter.registerDataSetObserver(new DataSetObserver() {
|
|
@Override
|
|
public void onChanged() {
|
|
if (!getActivity().isFinishing() && adapter.getCount() <= 0) {
|
|
updateEmptyView(adapter);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onInvalidated() {
|
|
if (!getActivity().isFinishing()) {
|
|
updateEmptyView(adapter);
|
|
}
|
|
}
|
|
});
|
|
setListAdapter(adapter);
|
|
}
|
|
|
|
@Override
|
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
|
super.onCreateOptionsMenu(menu, inflater);
|
|
inflater.inflate(R.menu.select_printer_activity, menu);
|
|
|
|
MenuItem searchItem = menu.findItem(R.id.action_search);
|
|
SearchView searchView = (SearchView) searchItem.getActionView();
|
|
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
|
@Override
|
|
public boolean onQueryTextSubmit(String query) {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean onQueryTextChange(String searchString) {
|
|
((DestinationAdapter) getListAdapter()).getFilter().filter(searchString);
|
|
return true;
|
|
}
|
|
});
|
|
searchView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
|
|
@Override
|
|
public void onViewAttachedToWindow(View view) {
|
|
view.announceForAccessibility(getString(
|
|
R.string.print_search_box_shown_utterance));
|
|
}
|
|
@Override
|
|
public void onViewDetachedFromWindow(View view) {
|
|
view.announceForAccessibility(getString(
|
|
R.string.print_search_box_hidden_utterance));
|
|
}
|
|
});
|
|
|
|
if (mAddPrinterServices.isEmpty()) {
|
|
menu.removeItem(R.id.action_add_printer);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onResume() {
|
|
updateAddPrintersAdapter();
|
|
getActivity().invalidateOptionsMenu();
|
|
super.onResume();
|
|
}
|
|
|
|
@Override
|
|
public void onListItemClick(ListView list, View view, int position, long id) {
|
|
PrinterInfo printer = (PrinterInfo) list.getAdapter().getItem(position);
|
|
Activity activity = getActivity();
|
|
if (activity instanceof OnPrinterSelectedListener) {
|
|
((OnPrinterSelectedListener) activity).onPrinterSelected(printer.getId());
|
|
} else {
|
|
throw new IllegalStateException("the host activity must implement"
|
|
+ " OnPrinterSelectedListener");
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
if (item.getItemId() == R.id.action_add_printer) {
|
|
showAddPrinterSelectionDialog();
|
|
return true;
|
|
}
|
|
return super.onOptionsItemSelected(item);
|
|
}
|
|
|
|
private void updateAddPrintersAdapter() {
|
|
mAddPrinterServices.clear();
|
|
|
|
// Get all enabled print services.
|
|
PrintManager printManager = (PrintManager) getActivity()
|
|
.getSystemService(Context.PRINT_SERVICE);
|
|
List<PrintServiceInfo> enabledServices = printManager.getEnabledPrintServices();
|
|
|
|
// No enabled print services - done.
|
|
if (enabledServices.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
// Find the services with valid add printers activities.
|
|
final int enabledServiceCount = enabledServices.size();
|
|
for (int i = 0; i < enabledServiceCount; i++) {
|
|
PrintServiceInfo enabledService = enabledServices.get(i);
|
|
|
|
// No add printers activity declared - done.
|
|
if (TextUtils.isEmpty(enabledService.getAddPrintersActivityName())) {
|
|
continue;
|
|
}
|
|
|
|
ServiceInfo serviceInfo = enabledService.getResolveInfo().serviceInfo;
|
|
ComponentName addPrintersComponentName = new ComponentName(
|
|
serviceInfo.packageName, enabledService.getAddPrintersActivityName());
|
|
Intent addPritnersIntent = new Intent()
|
|
.setComponent(addPrintersComponentName);
|
|
|
|
// The add printers activity is valid - add it.
|
|
PackageManager pm = getActivity().getPackageManager();
|
|
List<ResolveInfo> resolvedActivities = pm.queryIntentActivities(addPritnersIntent, 0);
|
|
if (!resolvedActivities.isEmpty()) {
|
|
// The activity is a component name, therefore it is one or none.
|
|
ActivityInfo activityInfo = resolvedActivities.get(0).activityInfo;
|
|
if (activityInfo.exported
|
|
&& (activityInfo.permission == null
|
|
|| pm.checkPermission(activityInfo.permission,
|
|
getActivity().getPackageName())
|
|
== PackageManager.PERMISSION_GRANTED)) {
|
|
mAddPrinterServices.add(enabledService);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void showAddPrinterSelectionDialog() {
|
|
FragmentTransaction transaction = getFragmentManager().beginTransaction();
|
|
Fragment oldFragment = getFragmentManager().findFragmentByTag(
|
|
FRAGMRNT_TAG_ADD_PRINTER_DIALOG);
|
|
if (oldFragment != null) {
|
|
transaction.remove(oldFragment);
|
|
}
|
|
AddPrinterAlertDialogFragment newFragment = new AddPrinterAlertDialogFragment();
|
|
Bundle arguments = new Bundle();
|
|
arguments.putParcelableArrayList(FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS,
|
|
mAddPrinterServices);
|
|
newFragment.setArguments(arguments);
|
|
transaction.add(newFragment, FRAGMRNT_TAG_ADD_PRINTER_DIALOG);
|
|
transaction.commit();
|
|
}
|
|
|
|
public void updateEmptyView(DestinationAdapter adapter) {
|
|
if (getListView().getEmptyView() == null) {
|
|
View emptyView = getActivity().findViewById(R.id.empty_print_state);
|
|
getListView().setEmptyView(emptyView);
|
|
}
|
|
TextView titleView = (TextView) getActivity().findViewById(R.id.title);
|
|
View progressBar = getActivity().findViewById(R.id.progress_bar);
|
|
if (adapter.getUnfilteredCount() <= 0) {
|
|
titleView.setText(R.string.print_searching_for_printers);
|
|
progressBar.setVisibility(View.VISIBLE);
|
|
} else {
|
|
titleView.setText(R.string.print_no_printers);
|
|
progressBar.setVisibility(View.GONE);
|
|
}
|
|
}
|
|
|
|
private void announceSearchResult() {
|
|
if (mAnnounceFilterResult == null) {
|
|
mAnnounceFilterResult = new AnnounceFilterResult();
|
|
}
|
|
mAnnounceFilterResult.post();
|
|
}
|
|
|
|
public static class AddPrinterAlertDialogFragment extends DialogFragment {
|
|
|
|
private String mAddPrintServiceItem;
|
|
|
|
@Override
|
|
@SuppressWarnings("unchecked")
|
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
|
|
.setTitle(R.string.choose_print_service);
|
|
|
|
final List<PrintServiceInfo> printServices = (List<PrintServiceInfo>) (List<?>)
|
|
getArguments().getParcelableArrayList(FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS);
|
|
|
|
final ArrayAdapter<String> adapter = new ArrayAdapter<String>(
|
|
getActivity(), android.R.layout.simple_list_item_1);
|
|
final int printServiceCount = printServices.size();
|
|
for (int i = 0; i < printServiceCount; i++) {
|
|
PrintServiceInfo printService = printServices.get(i);
|
|
adapter.add(printService.getResolveInfo().loadLabel(
|
|
getActivity().getPackageManager()).toString());
|
|
}
|
|
final String searchUri = Settings.Secure.getString(getActivity().getContentResolver(),
|
|
Settings.Secure.PRINT_SERVICE_SEARCH_URI);
|
|
final Intent marketIntent;
|
|
if (!TextUtils.isEmpty(searchUri)) {
|
|
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri));
|
|
if (getActivity().getPackageManager().resolveActivity(intent, 0) != null) {
|
|
marketIntent = intent;
|
|
mAddPrintServiceItem = getString(R.string.add_print_service_label);
|
|
adapter.add(mAddPrintServiceItem);
|
|
} else {
|
|
marketIntent = null;
|
|
}
|
|
} else {
|
|
marketIntent = null;
|
|
}
|
|
|
|
builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
String item = adapter.getItem(which);
|
|
if (item == mAddPrintServiceItem) {
|
|
try {
|
|
startActivity(marketIntent);
|
|
} catch (ActivityNotFoundException anfe) {
|
|
Log.w(LOG_TAG, "Couldn't start add printer activity", anfe);
|
|
}
|
|
} else {
|
|
PrintServiceInfo printService = printServices.get(which);
|
|
ComponentName componentName = new ComponentName(
|
|
printService.getResolveInfo().serviceInfo.packageName,
|
|
printService.getAddPrintersActivityName());
|
|
Intent intent = new Intent(Intent.ACTION_MAIN);
|
|
intent.setComponent(componentName);
|
|
try {
|
|
startActivity(intent);
|
|
} catch (ActivityNotFoundException anfe) {
|
|
Log.w(LOG_TAG, "Couldn't start settings activity", anfe);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
return builder.create();
|
|
}
|
|
}
|
|
|
|
private final class DestinationAdapter extends BaseAdapter
|
|
implements LoaderManager.LoaderCallbacks<List<PrinterInfo>>, Filterable {
|
|
|
|
private final Object mLock = new Object();
|
|
|
|
private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>();
|
|
|
|
private final List<PrinterInfo> mFilteredPrinters = new ArrayList<PrinterInfo>();
|
|
|
|
private CharSequence mLastSearchString;
|
|
|
|
public DestinationAdapter() {
|
|
getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this);
|
|
}
|
|
|
|
@Override
|
|
public Filter getFilter() {
|
|
return new Filter() {
|
|
@Override
|
|
protected FilterResults performFiltering(CharSequence constraint) {
|
|
synchronized (mLock) {
|
|
if (TextUtils.isEmpty(constraint)) {
|
|
return null;
|
|
}
|
|
FilterResults results = new FilterResults();
|
|
List<PrinterInfo> filteredPrinters = new ArrayList<PrinterInfo>();
|
|
String constraintLowerCase = constraint.toString().toLowerCase();
|
|
final int printerCount = mPrinters.size();
|
|
for (int i = 0; i < printerCount; i++) {
|
|
PrinterInfo printer = mPrinters.get(i);
|
|
if (printer.getName().toLowerCase().contains(constraintLowerCase)) {
|
|
filteredPrinters.add(printer);
|
|
}
|
|
}
|
|
results.values = filteredPrinters;
|
|
results.count = filteredPrinters.size();
|
|
return results;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
@SuppressWarnings("unchecked")
|
|
protected void publishResults(CharSequence constraint, FilterResults results) {
|
|
final boolean resultCountChanged;
|
|
synchronized (mLock) {
|
|
final int oldPrinterCount = mFilteredPrinters.size();
|
|
mLastSearchString = constraint;
|
|
mFilteredPrinters.clear();
|
|
if (results == null) {
|
|
mFilteredPrinters.addAll(mPrinters);
|
|
} else {
|
|
List<PrinterInfo> printers = (List<PrinterInfo>) results.values;
|
|
mFilteredPrinters.addAll(printers);
|
|
}
|
|
resultCountChanged = (oldPrinterCount != mFilteredPrinters.size());
|
|
}
|
|
if (resultCountChanged) {
|
|
announceSearchResult();
|
|
}
|
|
notifyDataSetChanged();
|
|
}
|
|
};
|
|
}
|
|
|
|
public int getUnfilteredCount() {
|
|
synchronized (mLock) {
|
|
return mPrinters.size();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getCount() {
|
|
synchronized (mLock) {
|
|
return mFilteredPrinters.size();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Object getItem(int position) {
|
|
synchronized (mLock) {
|
|
return mFilteredPrinters.get(position);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public long getItemId(int position) {
|
|
return position;
|
|
}
|
|
|
|
@Override
|
|
public View getDropDownView(int position, View convertView,
|
|
ViewGroup parent) {
|
|
return getView(position, convertView, parent);
|
|
}
|
|
|
|
@Override
|
|
public View getView(int position, View convertView, ViewGroup parent) {
|
|
if (convertView == null) {
|
|
convertView = getActivity().getLayoutInflater().inflate(
|
|
R.layout.printer_dropdown_item, parent, false);
|
|
}
|
|
|
|
convertView.setEnabled(isEnabled(position));
|
|
|
|
CharSequence title = null;
|
|
CharSequence subtitle = null;
|
|
Drawable icon = null;
|
|
|
|
PrinterInfo printer = (PrinterInfo) getItem(position);
|
|
title = printer.getName();
|
|
try {
|
|
PackageManager pm = getActivity().getPackageManager();
|
|
PackageInfo packageInfo = pm.getPackageInfo(printer.getId()
|
|
.getServiceName().getPackageName(), 0);
|
|
subtitle = packageInfo.applicationInfo.loadLabel(pm);
|
|
icon = packageInfo.applicationInfo.loadIcon(pm);
|
|
} catch (NameNotFoundException nnfe) {
|
|
/* ignore */
|
|
}
|
|
|
|
TextView titleView = (TextView) convertView.findViewById(R.id.title);
|
|
titleView.setText(title);
|
|
|
|
TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle);
|
|
if (!TextUtils.isEmpty(subtitle)) {
|
|
subtitleView.setText(subtitle);
|
|
subtitleView.setVisibility(View.VISIBLE);
|
|
} else {
|
|
subtitleView.setText(null);
|
|
subtitleView.setVisibility(View.GONE);
|
|
}
|
|
|
|
|
|
ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
|
|
if (icon != null) {
|
|
iconView.setImageDrawable(icon);
|
|
iconView.setVisibility(View.VISIBLE);
|
|
} else {
|
|
iconView.setVisibility(View.GONE);
|
|
}
|
|
|
|
return convertView;
|
|
}
|
|
|
|
@Override
|
|
public boolean isEnabled(int position) {
|
|
PrinterInfo printer = (PrinterInfo) getItem(position);
|
|
return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
|
|
}
|
|
|
|
@Override
|
|
public Loader<List<PrinterInfo>> onCreateLoader(int id, Bundle args) {
|
|
if (id == LOADER_ID_PRINTERS_LOADER) {
|
|
return new FusedPrintersProvider(getActivity());
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void onLoadFinished(Loader<List<PrinterInfo>> loader,
|
|
List<PrinterInfo> printers) {
|
|
synchronized (mLock) {
|
|
mPrinters.clear();
|
|
mPrinters.addAll(printers);
|
|
mFilteredPrinters.clear();
|
|
mFilteredPrinters.addAll(printers);
|
|
if (!TextUtils.isEmpty(mLastSearchString)) {
|
|
getFilter().filter(mLastSearchString);
|
|
}
|
|
}
|
|
notifyDataSetChanged();
|
|
}
|
|
|
|
@Override
|
|
public void onLoaderReset(Loader<List<PrinterInfo>> loader) {
|
|
synchronized (mLock) {
|
|
mPrinters.clear();
|
|
mFilteredPrinters.clear();
|
|
}
|
|
notifyDataSetInvalidated();
|
|
}
|
|
}
|
|
|
|
private final class AnnounceFilterResult implements Runnable {
|
|
private static final int SEARCH_RESULT_ANNOUNCEMENT_DELAY = 1000; // 1 sec
|
|
|
|
public void post() {
|
|
remove();
|
|
getListView().postDelayed(this, SEARCH_RESULT_ANNOUNCEMENT_DELAY);
|
|
}
|
|
|
|
public void remove() {
|
|
getListView().removeCallbacks(this);
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
final int count = getListView().getAdapter().getCount();
|
|
final String text;
|
|
if (count <= 0) {
|
|
text = getString(R.string.print_no_printers);
|
|
} else {
|
|
text = getActivity().getResources().getQuantityString(
|
|
R.plurals.print_search_result_count_utterance, count, count);
|
|
}
|
|
getListView().announceForAccessibility(text);
|
|
}
|
|
}
|
|
}
|