1/*
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.printservice.recommendation;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.annotation.StringRes;
22
23import com.android.internal.util.Preconditions;
24
25import java.net.InetAddress;
26import java.util.Collections;
27import java.util.List;
28
29/**
30 * Wrapper for a {@link PrintServicePlugin}, isolating issues with the plugin as good as possible
31 * from the {@link RecommendationServiceImpl service}.
32 */
33class RemotePrintServicePlugin implements PrintServicePlugin.PrinterDiscoveryCallback {
34    /** Lock for this object */
35    private final Object mLock = new Object();
36
37    /** The name of the print service. */
38    public final @StringRes int name;
39
40    /** If the print service if for more than a single vendor */
41    public final boolean recommendsMultiVendorService;
42
43    /** The package name of the full print service */
44    public final @NonNull CharSequence packageName;
45
46    /** Wrapped plugin */
47    private final @NonNull PrintServicePlugin mPlugin;
48
49    /** The printers discovered by the plugin */
50    private @NonNull List<InetAddress> mPrinters;
51
52    /** If the plugin is started by not yet stopped */
53    private boolean isRunning;
54
55    /** Listener for changes to {@link #mPrinters}. */
56    private @NonNull OnChangedListener mListener;
57
58    /**
59     * Create a new remote for a {@link PrintServicePlugin plugin}.
60     *
61     * @param plugin                       The plugin to be wrapped
62     * @param listener                     The listener to be notified about changes in this plugin
63     * @param recommendsMultiVendorService If the plugin detects printers of more than a single
64     *                                     vendor
65     *
66     * @throws PluginException If the plugin has issues while caching basic stub properties
67     */
68    public RemotePrintServicePlugin(@NonNull PrintServicePlugin plugin,
69            @NonNull OnChangedListener listener, boolean recommendsMultiVendorService)
70            throws PluginException {
71        mListener = listener;
72        mPlugin = plugin;
73        mPrinters = Collections.emptyList();
74
75        this.recommendsMultiVendorService = recommendsMultiVendorService;
76
77        // We handle any throwable to isolate our self from bugs in the plugin code.
78        // Cache simple properties to avoid having to deal with exceptions later in the code.
79        try {
80            name = Preconditions.checkArgumentPositive(mPlugin.getName(), "name");
81            packageName = Preconditions.checkStringNotEmpty(mPlugin.getPackageName(),
82                    "packageName");
83        } catch (Throwable e) {
84            throw new PluginException(mPlugin, "Cannot cache simple properties ", e);
85        }
86
87        isRunning = false;
88    }
89
90    /**
91     * Start the plugin. From now on there might be callbacks to the registered listener.
92     */
93    public void start()
94            throws PluginException {
95        // We handle any throwable to isolate our self from bugs in the stub code
96        try {
97            synchronized (mLock) {
98                isRunning = true;
99                mPlugin.start(this);
100            }
101        } catch (Throwable e) {
102            throw new PluginException(mPlugin, "Cannot start", e);
103        }
104    }
105
106    /**
107     * Stop the plugin. From this call on there will not be any more callbacks.
108     */
109    public void stop() throws PluginException {
110        // We handle any throwable to isolate our self from bugs in the stub code
111        try {
112            synchronized (mLock) {
113                mPlugin.stop();
114                isRunning = false;
115            }
116        } catch (Throwable e) {
117            throw new PluginException(mPlugin, "Cannot stop", e);
118        }
119    }
120
121    /**
122     * Get the current number of printers reported by the stub.
123     *
124     * @return The number of printers reported by the stub.
125     */
126    public @NonNull List<InetAddress> getPrinters() {
127        return mPrinters;
128    }
129
130    @Override
131    public void onChanged(@Nullable List<InetAddress> discoveredPrinters) {
132        synchronized (mLock) {
133            Preconditions.checkState(isRunning);
134
135            if (discoveredPrinters == null) {
136                mPrinters = Collections.emptyList();
137            } else {
138                mPrinters = Preconditions.checkCollectionElementsNotNull(discoveredPrinters,
139                        "discoveredPrinters");
140            }
141
142            mListener.onChanged();
143        }
144    }
145
146    /**
147     * Listener to listen for changes to {@link #getPrinters}
148     */
149    public interface OnChangedListener {
150        void onChanged();
151    }
152
153    /**
154     * Exception thrown if the stub has any issues.
155     */
156    public class PluginException extends Exception {
157        private PluginException(PrintServicePlugin plugin, String message, Throwable e) {
158            super(plugin + ": " + message, e);
159        }
160    }
161}
162