/* * 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.server.print; import android.Manifest; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.database.ContentObserver; import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.print.IPrintDocumentAdapter; import android.print.IPrintJobStateChangeListener; import android.print.IPrintManager; import android.print.IPrinterDiscoveryObserver; import android.print.PrintAttributes; import android.print.PrintJobId; import android.print.PrintJobInfo; import android.print.PrinterId; import android.printservice.PrintServiceInfo; import android.provider.Settings; import android.text.TextUtils; import android.util.SparseArray; import com.android.internal.R; import com.android.internal.content.PackageMonitor; import com.android.internal.os.BackgroundThread; import com.android.server.SystemService; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Iterator; import java.util.List; import java.util.Set; /** * SystemService wrapper for the PrintManager implementation. Publishes * Context.PRINT_SERVICE. * PrintManager implementation is contained within. */ public final class PrintManagerService extends SystemService { private final PrintManagerImpl mPrintManagerImpl; public PrintManagerService(Context context) { super(context); mPrintManagerImpl = new PrintManagerImpl(context); } @Override public void onStart() { publishBinderService(Context.PRINT_SERVICE, mPrintManagerImpl); } @Override public void onBootPhase(int phase) { if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { mPrintManagerImpl.systemRunning(); } } class PrintManagerImpl extends IPrintManager.Stub { private static final char COMPONENT_NAME_SEPARATOR = ':'; private static final String EXTRA_PRINT_SERVICE_COMPONENT_NAME = "EXTRA_PRINT_SERVICE_COMPONENT_NAME"; private final Object mLock = new Object(); private final Context mContext; private final SparseArray mUserStates = new SparseArray(); private int mCurrentUserId = UserHandle.USER_OWNER; PrintManagerImpl(Context context) { mContext = context; registerContentObservers(); registerBoradcastReceivers(); } public void systemRunning() { BackgroundThread.getHandler().post(new Runnable() { @Override public void run() { final UserState userState; synchronized (mLock) { userState = getCurrentUserStateLocked(); userState.updateIfNeededLocked(); } // This is the first time we switch to this user after boot, so // now is the time to remove obsolete print jobs since they // are from the last boot and no application would query them. userState.removeObsoletePrintJobs(); } }); } @Override public Bundle print(String printJobName, IPrintDocumentAdapter adapter, PrintAttributes attributes, String packageName, int appId, int userId) { final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId); final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); String resolvedPackageName = resolveCallingPackageNameEnforcingSecurity(packageName); final UserState userState; synchronized (mLock) { userState = getOrCreateUserStateLocked(resolvedUserId); } final long identity = Binder.clearCallingIdentity(); try { return userState.print(printJobName, adapter, attributes, resolvedPackageName, resolvedAppId); } finally { Binder.restoreCallingIdentity(identity); } } @Override public List getPrintJobInfos(int appId, int userId) { final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId); final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); final UserState userState; synchronized (mLock) { userState = getOrCreateUserStateLocked(resolvedUserId); } final long identity = Binder.clearCallingIdentity(); try { return userState.getPrintJobInfos(resolvedAppId); } finally { Binder.restoreCallingIdentity(identity); } } @Override public PrintJobInfo getPrintJobInfo(PrintJobId printJobId, int appId, int userId) { final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId); final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); final UserState userState; synchronized (mLock) { userState = getOrCreateUserStateLocked(resolvedUserId); } final long identity = Binder.clearCallingIdentity(); try { return userState.getPrintJobInfo(printJobId, resolvedAppId); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void cancelPrintJob(PrintJobId printJobId, int appId, int userId) { final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId); final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); final UserState userState; synchronized (mLock) { userState = getOrCreateUserStateLocked(resolvedUserId); } final long identity = Binder.clearCallingIdentity(); try { userState.cancelPrintJob(printJobId, resolvedAppId); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void restartPrintJob(PrintJobId printJobId, int appId, int userId) { final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId); final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); final UserState userState; synchronized (mLock) { userState = getOrCreateUserStateLocked(resolvedUserId); } final long identity = Binder.clearCallingIdentity(); try { userState.restartPrintJob(printJobId, resolvedAppId); } finally { Binder.restoreCallingIdentity(identity); } } @Override public List getEnabledPrintServices(int userId) { final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); final UserState userState; synchronized (mLock) { userState = getOrCreateUserStateLocked(resolvedUserId); } final long identity = Binder.clearCallingIdentity(); try { return userState.getEnabledPrintServices(); } finally { Binder.restoreCallingIdentity(identity); } } @Override public List getInstalledPrintServices(int userId) { final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); final UserState userState; synchronized (mLock) { userState = getOrCreateUserStateLocked(resolvedUserId); } final long identity = Binder.clearCallingIdentity(); try { return userState.getInstalledPrintServices(); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void createPrinterDiscoverySession(IPrinterDiscoveryObserver observer, int userId) { final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); final UserState userState; synchronized (mLock) { userState = getOrCreateUserStateLocked(resolvedUserId); } final long identity = Binder.clearCallingIdentity(); try { userState.createPrinterDiscoverySession(observer); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void destroyPrinterDiscoverySession(IPrinterDiscoveryObserver observer, int userId) { final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); final UserState userState; synchronized (mLock) { userState = getOrCreateUserStateLocked(resolvedUserId); } final long identity = Binder.clearCallingIdentity(); try { userState.destroyPrinterDiscoverySession(observer); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void startPrinterDiscovery(IPrinterDiscoveryObserver observer, List priorityList, int userId) { final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); final UserState userState; synchronized (mLock) { userState = getOrCreateUserStateLocked(resolvedUserId); } final long identity = Binder.clearCallingIdentity(); try { userState.startPrinterDiscovery(observer, priorityList); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void stopPrinterDiscovery(IPrinterDiscoveryObserver observer, int userId) { final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); final UserState userState; synchronized (mLock) { userState = getOrCreateUserStateLocked(resolvedUserId); } final long identity = Binder.clearCallingIdentity(); try { userState.stopPrinterDiscovery(observer); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void validatePrinters(List printerIds, int userId) { final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); final UserState userState; synchronized (mLock) { userState = getOrCreateUserStateLocked(resolvedUserId); } final long identity = Binder.clearCallingIdentity(); try { userState.validatePrinters(printerIds); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void startPrinterStateTracking(PrinterId printerId, int userId) { final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); final UserState userState; synchronized (mLock) { userState = getOrCreateUserStateLocked(resolvedUserId); } final long identity = Binder.clearCallingIdentity(); try { userState.startPrinterStateTracking(printerId); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void stopPrinterStateTracking(PrinterId printerId, int userId) { final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); final UserState userState; synchronized (mLock) { userState = getOrCreateUserStateLocked(resolvedUserId); } final long identity = Binder.clearCallingIdentity(); try { userState.stopPrinterStateTracking(printerId); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void addPrintJobStateChangeListener(IPrintJobStateChangeListener listener, int appId, int userId) throws RemoteException { final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId); final UserState userState; synchronized (mLock) { userState = getOrCreateUserStateLocked(resolvedUserId); } final long identity = Binder.clearCallingIdentity(); try { userState.addPrintJobStateChangeListener(listener, resolvedAppId); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void removePrintJobStateChangeListener(IPrintJobStateChangeListener listener, int userId) { final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); final UserState userState; synchronized (mLock) { userState = getOrCreateUserStateLocked(resolvedUserId); } final long identity = Binder.clearCallingIdentity(); try { userState.removePrintJobStateChangeListener(listener); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { pw.println("Permission Denial: can't dump PrintManager from from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); return; } synchronized (mLock) { final long identity = Binder.clearCallingIdentity(); try { pw.println("PRINT MANAGER STATE (dumpsys print)"); final int userStateCount = mUserStates.size(); for (int i = 0; i < userStateCount; i++) { UserState userState = mUserStates.valueAt(i); userState.dump(fd, pw, ""); pw.println(); } } finally { Binder.restoreCallingIdentity(identity); } } } private void registerContentObservers() { final Uri enabledPrintServicesUri = Settings.Secure.getUriFor( Settings.Secure.ENABLED_PRINT_SERVICES); ContentObserver observer = new ContentObserver(BackgroundThread.getHandler()) { @Override public void onChange(boolean selfChange, Uri uri) { if (enabledPrintServicesUri.equals(uri)) { synchronized (mLock) { UserState userState = getCurrentUserStateLocked(); userState.updateIfNeededLocked(); } } } }; mContext.getContentResolver().registerContentObserver(enabledPrintServicesUri, false, observer, UserHandle.USER_ALL); } private void registerBoradcastReceivers() { PackageMonitor monitor = new PackageMonitor() { @Override public boolean onPackageChanged(String packageName, int uid, String[] components) { synchronized (mLock) { UserState userState = getOrCreateUserStateLocked(getChangingUserId()); Iterator iterator = userState.getEnabledServices() .iterator(); while (iterator.hasNext()) { ComponentName componentName = iterator.next(); if (packageName.equals(componentName.getPackageName())) { userState.updateIfNeededLocked(); return true; } } } return false; } @Override public void onPackageRemoved(String packageName, int uid) { synchronized (mLock) { UserState userState = getOrCreateUserStateLocked(getChangingUserId()); Iterator iterator = userState.getEnabledServices() .iterator(); while (iterator.hasNext()) { ComponentName componentName = iterator.next(); if (packageName.equals(componentName.getPackageName())) { iterator.remove(); persistComponentNamesToSettingLocked( Settings.Secure.ENABLED_PRINT_SERVICES, userState.getEnabledServices(), getChangingUserId()); userState.updateIfNeededLocked(); return; } } } } @Override public boolean onHandleForceStop(Intent intent, String[] stoppedPackages, int uid, boolean doit) { synchronized (mLock) { UserState userState = getOrCreateUserStateLocked(getChangingUserId()); boolean stoppedSomePackages = false; Iterator iterator = userState.getEnabledServices() .iterator(); while (iterator.hasNext()) { ComponentName componentName = iterator.next(); String componentPackage = componentName.getPackageName(); for (String stoppedPackage : stoppedPackages) { if (componentPackage.equals(stoppedPackage)) { if (!doit) { return true; } stoppedSomePackages = true; break; } } } if (stoppedSomePackages) { userState.updateIfNeededLocked(); } return false; } } @Override public void onPackageAdded(String packageName, int uid) { Intent intent = new Intent(android.printservice.PrintService.SERVICE_INTERFACE); intent.setPackage(packageName); List installedServices = mContext.getPackageManager() .queryIntentServicesAsUser(intent, PackageManager.GET_SERVICES, getChangingUserId()); if (installedServices == null) { return; } final int installedServiceCount = installedServices.size(); for (int i = 0; i < installedServiceCount; i++) { ServiceInfo serviceInfo = installedServices.get(i).serviceInfo; ComponentName component = new ComponentName(serviceInfo.packageName, serviceInfo.name); String label = serviceInfo.loadLabel(mContext.getPackageManager()) .toString(); showEnableInstalledPrintServiceNotification(component, label, getChangingUserId()); } } private void persistComponentNamesToSettingLocked(String settingName, Set componentNames, int userId) { StringBuilder builder = new StringBuilder(); for (ComponentName componentName : componentNames) { if (builder.length() > 0) { builder.append(COMPONENT_NAME_SEPARATOR); } builder.append(componentName.flattenToShortString()); } Settings.Secure.putStringForUser(mContext.getContentResolver(), settingName, builder.toString(), userId); } }; // package changes monitor.register(mContext, BackgroundThread.getHandler().getLooper(), UserHandle.ALL, true); // user changes IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_USER_SWITCHED); intentFilter.addAction(Intent.ACTION_USER_REMOVED); mContext.registerReceiverAsUser(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (Intent.ACTION_USER_SWITCHED.equals(action)) { switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); } else if (Intent.ACTION_USER_REMOVED.equals(action)) { removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); } } }, UserHandle.ALL, intentFilter, null, BackgroundThread.getHandler()); } private UserState getCurrentUserStateLocked() { return getOrCreateUserStateLocked(mCurrentUserId); } private UserState getOrCreateUserStateLocked(int userId) { UserState userState = mUserStates.get(userId); if (userState == null) { userState = new UserState(mContext, userId, mLock); mUserStates.put(userId, userState); } return userState; } private void switchUser(int newUserId) { UserState userState; synchronized (mLock) { if (newUserId == mCurrentUserId) { return; } mCurrentUserId = newUserId; userState = mUserStates.get(mCurrentUserId); if (userState == null) { userState = getCurrentUserStateLocked(); userState.updateIfNeededLocked(); } else { userState.updateIfNeededLocked(); } } // This is the first time we switch to this user after boot, so // now is the time to remove obsolete print jobs since they // are from the last boot and no application would query them. userState.removeObsoletePrintJobs(); } private void removeUser(int removedUserId) { synchronized (mLock) { UserState userState = mUserStates.get(removedUserId); if (userState != null) { userState.destroyLocked(); mUserStates.remove(removedUserId); } } } private int resolveCallingAppEnforcingPermissions(int appId) { final int callingUid = Binder.getCallingUid(); if (callingUid == 0 || callingUid == Process.SYSTEM_UID || callingUid == Process.SHELL_UID) { return appId; } final int callingAppId = UserHandle.getAppId(callingUid); if (appId == callingAppId) { return appId; } if (mContext.checkCallingPermission( "com.android.printspooler.permission.ACCESS_ALL_PRINT_JOBS") != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Call from app " + callingAppId + " as app " + appId + " without com.android.printspooler.permission" + ".ACCESS_ALL_PRINT_JOBS"); } return appId; } private int resolveCallingUserEnforcingPermissions(int userId) { final int callingUid = Binder.getCallingUid(); if (callingUid == 0 || callingUid == Process.SYSTEM_UID || callingUid == Process.SHELL_UID) { return userId; } final int callingUserId = UserHandle.getUserId(callingUid); if (callingUserId == userId) { return userId; } if (mContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED || mContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) { if (userId == UserHandle.USER_CURRENT_OR_SELF) { return callingUserId; } throw new SecurityException("Call from user " + callingUserId + " as user " + userId + " without permission INTERACT_ACROSS_USERS or " + "INTERACT_ACROSS_USERS_FULL not allowed."); } if (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF) { return mCurrentUserId; } throw new IllegalArgumentException("Calling user can be changed to only " + "UserHandle.USER_CURRENT or UserHandle.USER_CURRENT_OR_SELF."); } private String resolveCallingPackageNameEnforcingSecurity(String packageName) { if (TextUtils.isEmpty(packageName)) { return null; } String[] packages = mContext.getPackageManager().getPackagesForUid( Binder.getCallingUid()); final int packageCount = packages.length; for (int i = 0; i < packageCount; i++) { if (packageName.equals(packages[i])) { return packageName; } } return null; } private void showEnableInstalledPrintServiceNotification(ComponentName component, String label, int userId) { UserHandle userHandle = new UserHandle(userId); Intent intent = new Intent(Settings.ACTION_PRINT_SETTINGS); intent.putExtra(EXTRA_PRINT_SERVICE_COMPONENT_NAME, component.flattenToString()); PendingIntent pendingIntent = PendingIntent.getActivityAsUser(mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT, null, userHandle); Notification.Builder builder = new Notification.Builder(mContext) .setSmallIcon(R.drawable.ic_print) .setContentTitle(mContext.getString(R.string.print_service_installed_title, label)) .setContentText(mContext.getString(R.string.print_service_installed_message)) .setContentIntent(pendingIntent) .setWhen(System.currentTimeMillis()) .setAutoCancel(true) .setShowWhen(true); NotificationManager notificationManager = (NotificationManager) mContext .getSystemService(Context.NOTIFICATION_SERVICE); String notificationTag = getClass().getName() + ":" + component.flattenToString(); notificationManager.notifyAsUser(notificationTag, 0, builder.build(), userHandle); } } }