/*
 * 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.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.DataSetObserver;
import android.net.Uri;
import android.os.Bundle;
import android.print.PrintManager;
import android.printservice.recommendation.RecommendationInfo;
import android.print.PrintServiceRecommendationsLoader;
import android.print.PrintServicesLoader;
import android.printservice.PrintServiceInfo;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
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.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.printspooler.R;

import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
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 #mRecommendedServicesAdapter} 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 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;
    private static final int LOADER_ID_RECOMMENDED_SERVICES = 3;
    private static final int LOADER_ID_ALL_SERVICES = 4;

    /**
     * The enabled services list. This is filled from the {@link #LOADER_ID_ENABLED_SERVICES}
     * loader in {@link PrintServiceInfoLoaderCallbacks#onLoadFinished}.
     */
    private EnabledServicesAdapter mEnabledServicesAdapter;

    /**
     * The disabled services list. This is filled from the {@link #LOADER_ID_DISABLED_SERVICES}
     * loader in {@link PrintServiceInfoLoaderCallbacks#onLoadFinished}.
     */
    private DisabledServicesAdapter mDisabledServicesAdapter;

    /**
     * The recommended services list. This is filled from the
     * {@link #LOADER_ID_RECOMMENDED_SERVICES} loader in
     * {@link PrintServicePrintServiceRecommendationLoaderCallbacks#onLoadFinished}.
     */
    private RecommendedServicesAdapter mRecommendedServicesAdapter;

    private static final String PKG_NAME_VENDING = "com.android.vending";
    private boolean mHasVending;
    private NoPrintServiceMessageAdapter mNoPrintServiceMessageAdapter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.add_printer_activity);

        try {
            getPackageManager().getPackageInfo(PKG_NAME_VENDING, 0);
            mHasVending = true;
        } catch (PackageManager.NameNotFoundException e) {
            mHasVending = false;
        }
        mEnabledServicesAdapter = new EnabledServicesAdapter();
        mDisabledServicesAdapter = new DisabledServicesAdapter();
        if (mHasVending) {
            mRecommendedServicesAdapter = new RecommendedServicesAdapter();
        } else {
            mNoPrintServiceMessageAdapter = new NoPrintServiceMessageAdapter();
        }

        ArrayList<ActionAdapter> adapterList = new ArrayList<>(3);
        adapterList.add(mEnabledServicesAdapter);
        if (mHasVending) {
            adapterList.add(mRecommendedServicesAdapter);
        }
        adapterList.add(mDisabledServicesAdapter);
        if (!mHasVending) {
            adapterList.add(mNoPrintServiceMessageAdapter);
        }

        setListAdapter(new CombinedAdapter(adapterList));

        getListView().setOnItemClickListener(this);

        PrintServiceInfoLoaderCallbacks printServiceLoaderCallbacks =
                new PrintServiceInfoLoaderCallbacks();

        getLoaderManager().initLoader(LOADER_ID_ENABLED_SERVICES, null, printServiceLoaderCallbacks);
        getLoaderManager().initLoader(LOADER_ID_DISABLED_SERVICES, null, printServiceLoaderCallbacks);
        if (mHasVending) {
            getLoaderManager().initLoader(LOADER_ID_RECOMMENDED_SERVICES, null,
                    new PrintServicePrintServiceRecommendationLoaderCallbacks());
        }
        getLoaderManager().initLoader(LOADER_ID_ALL_SERVICES, null, printServiceLoaderCallbacks);
    }

    @Override
    protected void onDestroy() {
        if (isFinishing()) {
            MetricsLogger.action(this, MetricsEvent.PRINT_ADD_PRINTERS,
                    mEnabledServicesAdapter.getCount());
        }

        super.onDestroy();
    }

    /**
     * Callbacks for the loaders operating on list of {@link PrintServiceInfo print service infos}.
     */
    private class PrintServiceInfoLoaderCallbacks implements
            LoaderManager.LoaderCallbacks<List<PrintServiceInfo>> {
        @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),
                            AddPrinterActivity.this, PrintManager.ENABLED_SERVICES);
                case LOADER_ID_DISABLED_SERVICES:
                    return new PrintServicesLoader(
                            (PrintManager) getSystemService(Context.PRINT_SERVICE),
                            AddPrinterActivity.this, PrintManager.DISABLED_SERVICES);
                case LOADER_ID_ALL_SERVICES:
                    return new PrintServicesLoader(
                            (PrintManager) getSystemService(Context.PRINT_SERVICE),
                            AddPrinterActivity.this, PrintManager.ALL_SERVICES);
                default:
                    // not reached
                    return null;
            }
        }


        @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;
                case LOADER_ID_ALL_SERVICES:
                    if (mHasVending) {
                        mRecommendedServicesAdapter.updateInstalledServices(data);
                    } else {
                        mNoPrintServiceMessageAdapter.updateInstalledServices(data);
                    }
                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;
                    case LOADER_ID_ALL_SERVICES:
                        if (mHasVending) {
                            mRecommendedServicesAdapter.updateInstalledServices(null);
                        } else {
                            mNoPrintServiceMessageAdapter.updateInstalledServices(null);
                        }
                        break;
                    default:
                        // not reached
                }
            }
        }
    }

    /**
     * Callbacks for the loaders operating on list of {@link RecommendationInfo print service
     * recommendations}.
     */
    private class PrintServicePrintServiceRecommendationLoaderCallbacks implements
            LoaderManager.LoaderCallbacks<List<RecommendationInfo>> {
        @Override
        public Loader<List<RecommendationInfo>> onCreateLoader(int id, Bundle args) {
            return new PrintServiceRecommendationsLoader(
                    (PrintManager) getSystemService(Context.PRINT_SERVICE),
                    AddPrinterActivity.this);
        }


        @Override
        public void onLoadFinished(Loader<List<RecommendationInfo>> loader,
                List<RecommendationInfo> data) {
            mRecommendedServicesAdapter.updateRecommendations(data);
        }

        @Override
        public void onLoaderReset(Loader<List<RecommendationInfo>> loader) {
            if (!isFinishing()) {
                mRecommendedServicesAdapter.updateRecommendations(null);
            }
        }
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        ((ActionAdapter) getListAdapter()).performAction(position);
    }

    /**
     * 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) {
            Intent intent = getAddPrinterIntent((PrintServiceInfo) getItem(position));
            if (intent != null) {
                try {
                    startActivity(intent);
                } catch (ActivityNotFoundException|SecurityException e) {
                    Log.e(LOG_TAG, "Cannot start add printers activity", e);
                }
            }
        }

        /**
         * Get the intent used to launch the add printers activity.
         *
         * @param service The service the printer should be added for
         *
         * @return The intent to launch the activity or null if the activity could not be launched.
         */
        private Intent getAddPrinterIntent(@NonNull PrintServiceInfo service) {
            String addPrinterActivityName = service.getAddPrintersActivityName();

            if (!TextUtils.isEmpty(addPrinterActivityName)) {
                Intent intent = new Intent(Intent.ACTION_MAIN);
                intent.setComponent(new ComponentName(service.getComponentName().getPackageName(),
                                addPrinterActivityName));

                List<ResolveInfo> resolvedActivities = getPackageManager().queryIntentActivities(
                        intent, 0);
                if (!resolvedActivities.isEmpty()) {
                    // The activity is a component name, therefore it is one or none.
                    if (resolvedActivities.get(0).activityInfo.exported) {
                        return intent;
                    }
                }
            }

            return null;
        }

        @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 (getAddPrinterIntent(service) == null) {
                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 {
        /** Package names of all installed print services */
        private @NonNull final ArraySet<String> mInstalledServices;

        /** All print service recommendations */
        private @Nullable List<RecommendationInfo> mRecommendations;

        /**
         * Sorted print service recommendations for services that are not installed
         *
         * @see #filterRecommendations
         */
        private @Nullable List<RecommendationInfo> mFilteredRecommendations;

        /**
         * Create a new adapter.
         */
        private RecommendedServicesAdapter() {
            mInstalledServices = new ArraySet<>();
        }

        @Override
        public int getCount() {
            if (mFilteredRecommendations == null) {
                return 2;
            } else {
                return mFilteredRecommendations.size() + 2;
            }
        }

        @Override
        public int getViewTypeCount() {
            return 3;
        }

        /**
         * @return The position the all services link is at.
         */
        private int getAllServicesPos() {
            return getCount() - 1;
        }

        @Override
        public int getItemViewType(int position) {
            if (position == 0) {
                return 0;
            } else if (getAllServicesPos() == position) {
                return 1;
            } else {
                return 2;
            }
        }

        @Override
        public Object getItem(int position) {
            if (position == 0 || position == getAllServicesPos()) {
                return null;
            } else {
                return mFilteredRecommendations.get(position - 1);
            }
        }

        @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;
            } else if (position == getAllServicesPos()) {
                if (convertView == null) {
                    convertView = getLayoutInflater().inflate(R.layout.all_print_services_list_item,
                            parent, false);
                }
            } else {
                RecommendationInfo recommendation = (RecommendationInfo) getItem(position);

                if (convertView == null) {
                    convertView = getLayoutInflater().inflate(
                            R.layout.print_service_recommendations_list_item, parent, false);
                }

                ((TextView) convertView.findViewById(R.id.title)).setText(recommendation.getName());

                ((TextView) convertView.findViewById(R.id.subtitle)).setText(getResources()
                        .getQuantityString(R.plurals.print_services_recommendation_subtitle,
                                recommendation.getNumDiscoveredPrinters(),
                                recommendation.getNumDiscoveredPrinters()));

                return convertView;
            }

            return convertView;
        }

        @Override
        public boolean isEnabled(int position) {
            return position != 0;
        }

        @Override
        public void performAction(@IntRange(from = 0) int position) {
            if (position == getAllServicesPos()) {
                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);
                    }
                }
            } else {
                RecommendationInfo recommendation = (RecommendationInfo) getItem(position);

                MetricsLogger.action(AddPrinterActivity.this,
                        MetricsEvent.ACTION_PRINT_RECOMMENDED_SERVICE_INSTALL,
                        recommendation.getPackageName().toString());

                try {
                    startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getString(
                            R.string.uri_package_details, recommendation.getPackageName()))));
                } catch (ActivityNotFoundException e) {
                    Log.e(LOG_TAG, "Cannot start market", e);
                }
            }
        }

        /**
         * Filter recommended services.
         */
        private void filterRecommendations() {
            if (mRecommendations == null) {
                mFilteredRecommendations = null;
            } else {
                mFilteredRecommendations = new ArrayList<>();

                // Filter out recommendations for already installed services
                final int numRecommendations = mRecommendations.size();
                for (int i = 0; i < numRecommendations; i++) {
                    RecommendationInfo recommendation = mRecommendations.get(i);

                    if (!mInstalledServices.contains(recommendation.getPackageName())) {
                        mFilteredRecommendations.add(recommendation);
                    }
                }
            }

            notifyDataSetChanged();
        }

        /**
         * Update the installed print services.
         *
         * @param services The new set of services
         */
        public void updateInstalledServices(List<PrintServiceInfo> services) {
            mInstalledServices.clear();

            if (services != null) {
                final int numServices = services.size();
                for (int i = 0; i < numServices; i++) {
                    mInstalledServices.add(services.get(i).getComponentName().getPackageName());
                }
            }

            filterRecommendations();
        }

        /**
         * Update the recommended print services.
         *
         * @param recommendations The new set of recommendations
         */
        public void updateRecommendations(List<RecommendationInfo> recommendations) {
            if (recommendations != null) {
                final Collator collator = Collator.getInstance();

                // Sort recommendations (early conditions are more important)
                // - higher number of discovered printers first
                // - single vendor services first
                // - alphabetically
                Collections.sort(recommendations,
                        new Comparator<RecommendationInfo>() {
                            @Override public int compare(RecommendationInfo o1,
                                    RecommendationInfo o2) {
                                if (o1.getNumDiscoveredPrinters() !=
                                        o2.getNumDiscoveredPrinters()) {
                                    return o2.getNumDiscoveredPrinters() -
                                            o1.getNumDiscoveredPrinters();
                                } else if (o1.recommendsMultiVendorService()
                                        != o2.recommendsMultiVendorService()) {
                                    if (o1.recommendsMultiVendorService()) {
                                        return 1;
                                    } else {
                                        return -1;
                                    }
                                } else {
                                    return collator.compare(o1.getName().toString(),
                                            o2.getName().toString());
                                }
                            }
                        });
            }

            mRecommendations = recommendations;

            filterRecommendations();
        }
    }

    private class NoPrintServiceMessageAdapter extends ActionAdapter {
        private boolean mHasPrintService;

        void updateInstalledServices(@Nullable List<PrintServiceInfo> services) {
            if (services == null || services.isEmpty()) {
                mHasPrintService = false;
            } else {
                mHasPrintService = true;
            }
            notifyDataSetChanged();
        }

        @Override
        public int getCount() {
            return mHasPrintService ? 0 : 1;
        }

        @Override
        public int getViewTypeCount() {
            return 1;
        }

        @Override
        public int getItemViewType(int position) {
            return 0;
        }

        @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 (convertView == null) {
                convertView = getLayoutInflater().inflate(R.layout.no_print_services_message,
                    parent, false);
            }
            return convertView;
        }

        @Override
        public boolean isEnabled(int position) {
            return position != 0;
        }

        @Override
        public void performAction(@IntRange(from = 0) int position) {
            return;
        }
    }
}
