blob: fa1f2323af570ac9b2ff0f4d1cfa025ab48b64e3 [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.server.print;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.printservice.recommendation.IRecommendationService;
import android.printservice.recommendation.IRecommendationServiceCallbacks;
import android.printservice.recommendation.RecommendationInfo;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import java.util.List;
import static android.content.pm.PackageManager.GET_META_DATA;
import static android.content.pm.PackageManager.GET_SERVICES;
import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
/**
* Connection to a remote print service recommendation service.
*/
class RemotePrintServiceRecommendationService {
private static final String LOG_TAG = "RemotePrintServiceRecS";
/** Lock for this object */
private final Object mLock = new Object();
/** Context used for the connection */
private @NonNull final Context mContext;
/** The connection to the service (if {@link #mIsBound bound}) */
@GuardedBy("mLock")
private @NonNull final Connection mConnection;
/** If the service is currently bound. */
@GuardedBy("mLock")
private boolean mIsBound;
/** The service once bound */
@GuardedBy("mLock")
private IRecommendationService mService;
/**
* Callbacks to be called when there are updates to the print service recommendations.
*/
public interface RemotePrintServiceRecommendationServiceCallbacks {
/**
* Called when there is an update list of print service recommendations.
*
* @param recommendations The new recommendations.
*/
void onPrintServiceRecommendationsUpdated(
@Nullable List<RecommendationInfo> recommendations);
}
/**
* @return The intent that is used to connect to the print service recommendation service.
*/
private Intent getServiceIntent(@NonNull UserHandle userHandle) throws Exception {
List<ResolveInfo> installedServices = mContext.getPackageManager()
.queryIntentServicesAsUser(new Intent(
android.printservice.recommendation.RecommendationService.SERVICE_INTERFACE),
GET_SERVICES | GET_META_DATA | MATCH_DEBUG_TRIAGED_MISSING,
userHandle.getIdentifier());
if (installedServices.size() != 1) {
throw new Exception(installedServices.size() + " instead of exactly one service found");
}
ResolveInfo installedService = installedServices.get(0);
ComponentName serviceName = new ComponentName(
installedService.serviceInfo.packageName,
installedService.serviceInfo.name);
ApplicationInfo appInfo = mContext.getPackageManager()
.getApplicationInfo(installedService.serviceInfo.packageName, 0);
if (appInfo == null) {
throw new Exception("Cannot read appInfo for service");
}
if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
throw new Exception("Service is not part of the system");
}
if (!android.Manifest.permission.BIND_PRINT_RECOMMENDATION_SERVICE.equals(
installedService.serviceInfo.permission)) {
throw new Exception("Service " + serviceName.flattenToShortString()
+ " does not require permission "
+ android.Manifest.permission.BIND_PRINT_RECOMMENDATION_SERVICE);
}
Intent serviceIntent = new Intent();
serviceIntent.setComponent(serviceName);
return serviceIntent;
}
/**
* Open a new connection to a {@link IRecommendationService remote print service
* recommendation service}.
*
* @param context The context establishing the connection
* @param userHandle The user the connection is for
* @param callbacks The callbacks to call by the service
*/
RemotePrintServiceRecommendationService(@NonNull Context context,
@NonNull UserHandle userHandle,
@NonNull RemotePrintServiceRecommendationServiceCallbacks callbacks) {
mContext = context;
mConnection = new Connection(callbacks);
try {
Intent serviceIntent = getServiceIntent(userHandle);
synchronized (mLock) {
mIsBound = mContext.bindServiceAsUser(serviceIntent, mConnection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, userHandle);
if (!mIsBound) {
throw new Exception("Failed to bind to service " + serviceIntent);
}
}
} catch (Exception e) {
Log.e(LOG_TAG, "Could not connect to print service recommendation service", e);
}
}
/**
* Terminate the connection to the {@link IRecommendationService remote print
* service recommendation service}.
*/
void close() {
synchronized (mLock) {
if (mService != null) {
try {
mService.registerCallbacks(null);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Could not unregister callbacks", e);
}
mService = null;
}
if (mIsBound) {
mContext.unbindService(mConnection);
mIsBound = false;
}
}
}
@Override
protected void finalize() throws Throwable {
if (mIsBound || mService != null) {
Log.w(LOG_TAG, "Service still connected on finalize()");
close();
}
super.finalize();
}
/**
* Connection to the service.
*/
private class Connection implements ServiceConnection {
private final RemotePrintServiceRecommendationServiceCallbacks mCallbacks;
public Connection(@NonNull RemotePrintServiceRecommendationServiceCallbacks callbacks) {
mCallbacks = callbacks;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {
mService = (IRecommendationService)IRecommendationService.Stub.asInterface(service);
try {
mService.registerCallbacks(new IRecommendationServiceCallbacks.Stub() {
@Override
public void onRecommendationsUpdated(
List<RecommendationInfo> recommendations) {
synchronized (mLock) {
if (mIsBound && mService != null) {
if (recommendations != null) {
Preconditions.checkCollectionElementsNotNull(
recommendations, "recommendation");
}
mCallbacks.onPrintServiceRecommendationsUpdated(
recommendations);
}
}
}
});
} catch (RemoteException e) {
Log.e(LOG_TAG, "Could not register callbacks", e);
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.w(LOG_TAG, "Unexpected termination of connection");
synchronized (mLock) {
mService = null;
}
}
}
}