/* * 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.ui; 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.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; 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.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityManager; import android.widget.AdapterView; import android.widget.AdapterView.AdapterContextMenuInfo; 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 com.android.printspooler.R; import java.util.ArrayList; import java.util.List; /** * This is an activity for selecting a printer. */ public final class SelectPrinterActivity extends Activity { private static final String LOG_TAG = "SelectPrinterFragment"; public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID"; private static final String FRAGMENT_TAG_ADD_PRINTER_DIALOG = "FRAGMENT_TAG_ADD_PRINTER_DIALOG"; private static final String FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS = "FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS"; private static final String EXTRA_PRINTER_ID = "EXTRA_PRINTER_ID"; private final ArrayList<PrintServiceInfo> mAddPrinterServices = new ArrayList<>(); private PrinterRegistry mPrinterRegistry; private ListView mListView; private AnnounceFilterResult mAnnounceFilterResult; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getActionBar().setIcon(R.drawable.ic_print); setContentView(R.layout.select_printer_activity); mPrinterRegistry = new PrinterRegistry(this, null); // Hook up the list view. mListView = (ListView) findViewById(android.R.id.list); final DestinationAdapter adapter = new DestinationAdapter(); adapter.registerDataSetObserver(new DataSetObserver() { @Override public void onChanged() { if (!isFinishing() && adapter.getCount() <= 0) { updateEmptyView(adapter); } } @Override public void onInvalidated() { if (!isFinishing()) { updateEmptyView(adapter); } } }); mListView.setAdapter(adapter); mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (!((DestinationAdapter) mListView.getAdapter()).isActionable(position)) { return; } PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position); onPrinterSelected(printer.getId()); } }); registerForContextMenu(mListView); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().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) mListView.getAdapter()).getFilter().filter(searchString); return true; } }); searchView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View view) { if (AccessibilityManager.getInstance(SelectPrinterActivity.this).isEnabled()) { view.announceForAccessibility(getString( R.string.print_search_box_shown_utterance)); } } @Override public void onViewDetachedFromWindow(View view) { if (!isFinishing() && AccessibilityManager.getInstance( SelectPrinterActivity.this).isEnabled()) { view.announceForAccessibility(getString( R.string.print_search_box_hidden_utterance)); } } }); if (mAddPrinterServices.isEmpty()) { menu.removeItem(R.id.action_add_printer); } return true; } @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) { if (view == mListView) { final int position = ((AdapterContextMenuInfo) menuInfo).position; PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position); menu.setHeaderTitle(printer.getName()); // Add the select menu item if applicable. if (printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE) { MenuItem selectItem = menu.add(Menu.NONE, R.string.print_select_printer, Menu.NONE, R.string.print_select_printer); Intent intent = new Intent(); intent.putExtra(EXTRA_PRINTER_ID, printer.getId()); selectItem.setIntent(intent); } // Add the forget menu item if applicable. if (mPrinterRegistry.isFavoritePrinter(printer.getId())) { MenuItem forgetItem = menu.add(Menu.NONE, R.string.print_forget_printer, Menu.NONE, R.string.print_forget_printer); Intent intent = new Intent(); intent.putExtra(EXTRA_PRINTER_ID, printer.getId()); forgetItem.setIntent(intent); } } } @Override public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { case R.string.print_select_printer: { PrinterId printerId = item.getIntent().getParcelableExtra(EXTRA_PRINTER_ID); onPrinterSelected(printerId); } return true; case R.string.print_forget_printer: { PrinterId printerId = item.getIntent().getParcelableExtra(EXTRA_PRINTER_ID); mPrinterRegistry.forgetFavoritePrinter(printerId); } return true; } return false; } @Override public void onResume() { super.onResume(); updateServicesWithAddPrinterActivity(); invalidateOptionsMenu(); } @Override public void onPause() { if (mAnnounceFilterResult != null) { mAnnounceFilterResult.remove(); } super.onPause(); } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.action_add_printer) { showAddPrinterSelectionDialog(); return true; } return super.onOptionsItemSelected(item); } private void onPrinterSelected(PrinterId printerId) { Intent intent = new Intent(); intent.putExtra(INTENT_EXTRA_PRINTER_ID, printerId); setResult(RESULT_OK, intent); finish(); } private void updateServicesWithAddPrinterActivity() { mAddPrinterServices.clear(); // Get all enabled print services. PrintManager printManager = (PrintManager) 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 - next. 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 = 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, getPackageName()) == PackageManager.PERMISSION_GRANTED)) { mAddPrinterServices.add(enabledService); } } } } private void showAddPrinterSelectionDialog() { FragmentTransaction transaction = getFragmentManager().beginTransaction(); Fragment oldFragment = getFragmentManager().findFragmentByTag( FRAGMENT_TAG_ADD_PRINTER_DIALOG); if (oldFragment != null) { transaction.remove(oldFragment); } AddPrinterAlertDialogFragment newFragment = new AddPrinterAlertDialogFragment(); Bundle arguments = new Bundle(); arguments.putParcelableArrayList(FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS, mAddPrinterServices); newFragment.setArguments(arguments); transaction.add(newFragment, FRAGMENT_TAG_ADD_PRINTER_DIALOG); transaction.commit(); } public void updateEmptyView(DestinationAdapter adapter) { if (mListView.getEmptyView() == null) { View emptyView = findViewById(R.id.empty_print_state); mListView.setEmptyView(emptyView); } TextView titleView = (TextView) findViewById(R.id.title); View progressBar = 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 announceSearchResultIfNeeded() { if (AccessibilityManager.getInstance(this).isEnabled()) { 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(FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS); final ArrayAdapter<String> adapter = new ArrayAdapter<>( 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 viewIntent; if (!TextUtils.isEmpty(searchUri)) { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri)); if (getActivity().getPackageManager().resolveActivity(intent, 0) != null) { viewIntent = intent; mAddPrintServiceItem = getString(R.string.add_print_service_label); adapter.add(mAddPrintServiceItem); } else { viewIntent = null; } } else { viewIntent = null; } builder.setAdapter(adapter, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String item = adapter.getItem(which); if (item.equals(mAddPrintServiceItem)) { try { startActivity(viewIntent); } 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 add printer activity", anfe); } } } }); return builder.create(); } } private final class DestinationAdapter extends BaseAdapter implements Filterable { private final Object mLock = new Object(); private final List<PrinterInfo> mPrinters = new ArrayList<>(); private final List<PrinterInfo> mFilteredPrinters = new ArrayList<>(); private CharSequence mLastSearchString; public DestinationAdapter() { mPrinterRegistry.setOnPrintersChangeListener(new PrinterRegistry.OnPrintersChangeListener() { @Override public void onPrintersChanged(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 onPrintersInvalid() { synchronized (mLock) { mPrinters.clear(); mFilteredPrinters.clear(); } notifyDataSetInvalidated(); } }); } @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<>(); 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) { announceSearchResultIfNeeded(); } 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 = getLayoutInflater().inflate( R.layout.printer_list_item, parent, false); } convertView.setEnabled(isActionable(position)); PrinterInfo printer = (PrinterInfo) getItem(position); CharSequence title = printer.getName(); CharSequence subtitle = null; Drawable icon = null; try { PackageManager pm = 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; } public boolean isActionable(int position) { PrinterInfo printer = (PrinterInfo) getItem(position); return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE; } } private final class AnnounceFilterResult implements Runnable { private static final int SEARCH_RESULT_ANNOUNCEMENT_DELAY = 1000; // 1 sec public void post() { remove(); mListView.postDelayed(this, SEARCH_RESULT_ANNOUNCEMENT_DELAY); } public void remove() { mListView.removeCallbacks(this); } @Override public void run() { final int count = mListView.getAdapter().getCount(); final String text; if (count <= 0) { text = getString(R.string.print_no_printers); } else { text = getResources().getQuantityString( R.plurals.print_search_result_count_utterance, count, count); } mListView.announceForAccessibility(text); } } }