Always send volume UUID with installd commands.
Since packages can be moved to other volumes, all relevant commands
to installd now require an explicit volume UUID parameter.
Bug: 20275577
Change-Id: Ie84f5bc43c7aada5800b8d71692c7928b42b965e
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index b43c462..d5cc8cc 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -1691,9 +1691,13 @@
showUsage();
return 1;
}
+ String volumeUuid = nextArg();
+ if ("internal".equals(volumeUuid)) {
+ volumeUuid = null;
+ }
ClearDataObserver obs = new ClearDataObserver();
try {
- mPm.freeStorageAndNotify(sizeVal, obs);
+ mPm.freeStorageAndNotify(volumeUuid, sizeVal, obs);
synchronized (obs) {
while (!obs.finished) {
try {
@@ -1884,7 +1888,7 @@
System.err.println(" pm set-install-location [0/auto] [1/internal] [2/external]");
System.err.println(" pm get-install-location");
System.err.println(" pm set-permission-enforced PERMISSION [true|false]");
- System.err.println(" pm trim-caches DESIRED_FREE_SPACE");
+ System.err.println(" pm trim-caches DESIRED_FREE_SPACE [internal|UUID]");
System.err.println(" pm create-user [--profileOf USER_ID] [--managed] USER_NAME");
System.err.println(" pm remove-user USER_ID");
System.err.println(" pm get-max-users");
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index b9ddff0..dfe7e18 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1520,19 +1520,21 @@
// Should never happen!
}
}
+
@Override
- public void freeStorageAndNotify(long idealStorageSize, IPackageDataObserver observer) {
+ public void freeStorageAndNotify(String volumeUuid, long idealStorageSize,
+ IPackageDataObserver observer) {
try {
- mPM.freeStorageAndNotify(idealStorageSize, observer);
+ mPM.freeStorageAndNotify(volumeUuid, idealStorageSize, observer);
} catch (RemoteException e) {
// Should never happen!
}
}
@Override
- public void freeStorage(long freeStorageSize, IntentSender pi) {
+ public void freeStorage(String volumeUuid, long freeStorageSize, IntentSender pi) {
try {
- mPM.freeStorage(freeStorageSize, pi);
+ mPM.freeStorage(volumeUuid, freeStorageSize, pi);
} catch (RemoteException e) {
// Should never happen!
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index c2580c0..447c668 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -328,7 +328,7 @@
* @param observer call back used to notify when
* the operation is completed
*/
- void freeStorageAndNotify(in long freeStorageSize,
+ void freeStorageAndNotify(in String volumeUuid, in long freeStorageSize,
IPackageDataObserver observer);
/**
@@ -352,7 +352,7 @@
* notify when the operation is completed.May be null
* to indicate that no call back is desired.
*/
- void freeStorage(in long freeStorageSize,
+ void freeStorage(in String volumeUuid, in long freeStorageSize,
in IntentSender pi);
/**
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a128872..a0cec50 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3798,7 +3798,13 @@
* @hide
*/
// @SystemApi
- public abstract void freeStorageAndNotify(long freeStorageSize, IPackageDataObserver observer);
+ public void freeStorageAndNotify(long freeStorageSize, IPackageDataObserver observer) {
+ freeStorageAndNotify(null, freeStorageSize, observer);
+ }
+
+ /** {@hide} */
+ public abstract void freeStorageAndNotify(String volumeUuid, long freeStorageSize,
+ IPackageDataObserver observer);
/**
* Free storage by deleting LRU sorted list of cache files across
@@ -3823,7 +3829,12 @@
*
* @hide
*/
- public abstract void freeStorage(long freeStorageSize, IntentSender pi);
+ public void freeStorage(long freeStorageSize, IntentSender pi) {
+ freeStorage(null, freeStorageSize, pi);
+ }
+
+ /** {@hide} */
+ public abstract void freeStorage(String volumeUuid, long freeStorageSize, IntentSender pi);
/**
* Retrieve the size information for a package.
diff --git a/core/tests/coretests/src/android/content/pm/AppCacheTest.java b/core/tests/coretests/src/android/content/pm/AppCacheTest.java
index aae55e8..54316d5 100644
--- a/core/tests/coretests/src/android/content/pm/AppCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/AppCacheTest.java
@@ -490,7 +490,7 @@
PackageDataObserver observer = new PackageDataObserver();
//wait on observer
synchronized(observer) {
- getPm().freeStorageAndNotify(idealStorageSize, observer);
+ getPm().freeStorageAndNotify(null, idealStorageSize, observer);
long waitTime = 0;
while(!observer.isDone() || (waitTime > MAX_WAIT_TIME)) {
observer.wait(WAIT_TIME_INCR);
@@ -515,7 +515,7 @@
try {
// Spin lock waiting for call back
synchronized(r) {
- getPm().freeStorage(idealStorageSize, pi.getIntentSender());
+ getPm().freeStorage(null, idealStorageSize, pi.getIntentSender());
long waitTime = 0;
while(!r.isDone() && (waitTime < MAX_WAIT_TIME)) {
r.wait(WAIT_TIME_INCR);
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index a32363d..0f3b4e6 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -48,6 +48,9 @@
if (TextUtils.isEmpty(arg)) {
return "!";
} else {
+ if (arg.indexOf('\0') != -1 || arg.indexOf(' ') != -1) {
+ throw new IllegalArgumentException(arg);
+ }
return arg;
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 46db2d8..89ca00e 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -371,7 +371,7 @@
final long deltaBytes = lengthBytes - stat.st_size;
// Only need to free up space when writing to internal stage
if (stageDir != null && deltaBytes > 0) {
- mPm.freeStorage(deltaBytes);
+ mPm.freeStorage(params.volumeUuid, deltaBytes);
}
Libcore.os.posix_fallocate(targetFd, 0, lengthBytes);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 331683b..24cc909 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1533,6 +1533,8 @@
public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
if (vol.type == VolumeInfo.TYPE_PRIVATE) {
if (vol.state == VolumeInfo.STATE_MOUNTED) {
+ // TODO: ensure that private directories exist for all active users
+ // TODO: remove user data whose serial number doesn't match
loadPrivatePackages(vol);
} else if (vol.state == VolumeInfo.STATE_EJECTING) {
unloadPrivatePackages(vol);
@@ -1967,7 +1969,7 @@
psit.remove();
logCriticalInfo(Log.WARN, "System package " + ps.name
+ " no longer exists; wiping its data");
- removeDataDirsLI(ps.name);
+ removeDataDirsLI(null, ps.name);
} else {
final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(ps.name);
if (disabledPs.codePath == null || !disabledPs.codePath.exists()) {
@@ -2012,7 +2014,7 @@
if (deletedPkg == null) {
msg = "Updated system package " + deletedAppName
+ " no longer exists; wiping its data";
- removeDataDirsLI(deletedAppName);
+ removeDataDirsLI(null, deletedAppName);
} else {
msg = "Updated system app + " + deletedAppName
+ " no longer present; removing system privileges for "
@@ -2128,8 +2130,9 @@
mIsUpgrade = !Build.FINGERPRINT.equals(mSettings.mFingerprint);
if (mIsUpgrade && !onlyCore) {
Slog.i(TAG, "Build fingerprint changed; clearing code caches");
- for (String pkgName : mSettings.mPackages.keySet()) {
- deleteCodeCacheDirsLI(pkgName);
+ for (int i = 0; i < mSettings.mPackages.size(); i++) {
+ final PackageSetting ps = mSettings.mPackages.valueAt(i);
+ deleteCodeCacheDirsLI(ps.volumeUuid, ps.name);
}
mSettings.mFingerprint = Build.FINGERPRINT;
}
@@ -2309,7 +2312,7 @@
void cleanupInstallFailedPackage(PackageSetting ps) {
logCriticalInfo(Log.WARN, "Cleaning up incompletely installed app: " + ps.name);
- removeDataDirsLI(ps.name);
+ removeDataDirsLI(ps.volumeUuid, ps.name);
if (ps.codePath != null) {
if (ps.codePath.isDirectory()) {
mInstaller.rmPackageDir(ps.codePath.getAbsolutePath());
@@ -2604,9 +2607,9 @@
return null;
}
-
@Override
- public void freeStorageAndNotify(final long freeStorageSize, final IPackageDataObserver observer) {
+ public void freeStorageAndNotify(final String volumeUuid, final long freeStorageSize,
+ final IPackageDataObserver observer) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CLEAR_APP_CACHE, null);
// Queue up an async operation since clearing cache may take a little while.
@@ -2615,7 +2618,7 @@
mHandler.removeCallbacks(this);
int retCode = -1;
synchronized (mInstallLock) {
- retCode = mInstaller.freeCache(freeStorageSize);
+ retCode = mInstaller.freeCache(volumeUuid, freeStorageSize);
if (retCode < 0) {
Slog.w(TAG, "Couldn't clear application caches");
}
@@ -2632,7 +2635,8 @@
}
@Override
- public void freeStorage(final long freeStorageSize, final IntentSender pi) {
+ public void freeStorage(final String volumeUuid, final long freeStorageSize,
+ final IntentSender pi) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CLEAR_APP_CACHE, null);
// Queue up an async operation since clearing cache may take a little while.
@@ -2641,7 +2645,7 @@
mHandler.removeCallbacks(this);
int retCode = -1;
synchronized (mInstallLock) {
- retCode = mInstaller.freeCache(freeStorageSize);
+ retCode = mInstaller.freeCache(volumeUuid, freeStorageSize);
if (retCode < 0) {
Slog.w(TAG, "Couldn't clear application caches");
}
@@ -2660,9 +2664,9 @@
});
}
- void freeStorage(long freeStorageSize) throws IOException {
+ void freeStorage(String volumeUuid, long freeStorageSize) throws IOException {
synchronized (mInstallLock) {
- if (mInstaller.freeCache(freeStorageSize) < 0) {
+ if (mInstaller.freeCache(volumeUuid, freeStorageSize) < 0) {
throw new IOException("Failed to free enough space");
}
}
@@ -5481,15 +5485,15 @@
return true;
}
- private int createDataDirsLI(String packageName, int uid, String seinfo) {
+ private int createDataDirsLI(String volumeUuid, String packageName, int uid, String seinfo) {
int[] users = sUserManager.getUserIds();
- int res = mInstaller.install(packageName, uid, uid, seinfo);
+ int res = mInstaller.install(volumeUuid, packageName, uid, uid, seinfo);
if (res < 0) {
return res;
}
for (int user : users) {
if (user != 0) {
- res = mInstaller.createUserData(packageName,
+ res = mInstaller.createUserData(volumeUuid, packageName,
UserHandle.getUid(user, uid), user, seinfo);
if (res < 0) {
return res;
@@ -5499,11 +5503,11 @@
return res;
}
- private int removeDataDirsLI(String packageName) {
+ private int removeDataDirsLI(String volumeUuid, String packageName) {
int[] users = sUserManager.getUserIds();
int res = 0;
for (int user : users) {
- int resInner = mInstaller.remove(packageName, user);
+ int resInner = mInstaller.remove(volumeUuid, packageName, user);
if (resInner < 0) {
res = resInner;
}
@@ -5512,11 +5516,11 @@
return res;
}
- private int deleteCodeCacheDirsLI(String packageName) {
+ private int deleteCodeCacheDirsLI(String volumeUuid, String packageName) {
int[] users = sUserManager.getUserIds();
int res = 0;
for (int user : users) {
- int resInner = mInstaller.deleteCodeCacheFiles(packageName, user);
+ int resInner = mInstaller.deleteCodeCacheFiles(volumeUuid, packageName, user);
if (resInner < 0) {
res = resInner;
}
@@ -5651,7 +5655,7 @@
return res;
} finally {
if (!success && (scanFlags & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
- removeDataDirsLI(pkg.packageName);
+ removeDataDirsLI(pkg.volumeUuid, pkg.packageName);
}
}
}
@@ -6021,8 +6025,8 @@
// This is probably because the system was stopped while
// installd was in the middle of messing with its libs
// directory. Ask installd to fix that.
- int ret = mInstaller.fixUid(pkgName, pkg.applicationInfo.uid,
- pkg.applicationInfo.uid);
+ int ret = mInstaller.fixUid(pkg.volumeUuid, pkgName,
+ pkg.applicationInfo.uid, pkg.applicationInfo.uid);
if (ret >= 0) {
recovered = true;
String msg = "Package " + pkg.packageName
@@ -6035,7 +6039,7 @@
|| (scanFlags&SCAN_BOOTING) != 0)) {
// If this is a system app, we can at least delete its
// current data so the application will still work.
- int ret = removeDataDirsLI(pkgName);
+ int ret = removeDataDirsLI(pkg.volumeUuid, pkgName);
if (ret >= 0) {
// TODO: Kill the processes first
// Old data gone!
@@ -6049,8 +6053,8 @@
recovered = true;
// And now re-install the app.
- ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid,
- pkg.applicationInfo.seinfo);
+ ret = createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid,
+ pkg.applicationInfo.seinfo);
if (ret == -1) {
// Ack should not happen!
msg = prefix + pkg.packageName
@@ -6093,8 +6097,8 @@
pkg.applicationInfo.dataDir = dataPath.getPath();
if (mShouldRestoreconData) {
Slog.i(TAG, "SELinux relabeling of " + pkg.packageName + " issued.");
- mInstaller.restoreconData(pkg.packageName, pkg.applicationInfo.seinfo,
- pkg.applicationInfo.uid);
+ mInstaller.restoreconData(pkg.volumeUuid, pkg.packageName,
+ pkg.applicationInfo.seinfo, pkg.applicationInfo.uid);
}
} else {
if (DEBUG_PACKAGE_SCANNING) {
@@ -6102,8 +6106,8 @@
Log.v(TAG, "Want this data dir: " + dataPath);
}
//invoke installer to do the actual installation
- int ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid,
- pkg.applicationInfo.seinfo);
+ int ret = createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid,
+ pkg.applicationInfo.seinfo);
if (ret < 0) {
// Error from installer
throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
@@ -9590,7 +9594,7 @@
final long sizeBytes = mContainerService.calculateInstalledSize(
origin.resolvedPath, isForwardLocked(), packageAbiOverride);
- if (mInstaller.freeCache(sizeBytes + lowThreshold) >= 0) {
+ if (mInstaller.freeCache(null, sizeBytes + lowThreshold) >= 0) {
pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath,
installFlags, packageAbiOverride);
}
@@ -10818,7 +10822,7 @@
sendResourcesChangedBroadcast(false, true, pkgList, uidArray, null);
}
- deleteCodeCacheDirsLI(pkgName);
+ deleteCodeCacheDirsLI(pkg.volumeUuid, pkgName);
try {
final PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags,
scanFlags | SCAN_UPDATE_TIME, System.currentTimeMillis(), user);
@@ -10930,7 +10934,7 @@
}
// Successfully disabled the old package. Now proceed with re-installation
- deleteCodeCacheDirsLI(packageName);
+ deleteCodeCacheDirsLI(pkg.volumeUuid, packageName);
res.returnCode = PackageManager.INSTALL_SUCCEEDED;
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
@@ -11661,7 +11665,7 @@
}
}
if ((flags&PackageManager.DELETE_KEEP_DATA) == 0) {
- removeDataDirsLI(packageName);
+ removeDataDirsLI(ps.volumeUuid, packageName);
schedulePackageCleaning(packageName, UserHandle.USER_ALL, true);
}
// writer
@@ -11966,7 +11970,7 @@
outInfo.removedAppId = appId;
outInfo.removedUsers = new int[] {removeUser};
}
- mInstaller.clearUserData(packageName, removeUser);
+ mInstaller.clearUserData(ps.volumeUuid, packageName, removeUser);
removeKeystoreDataIfNeeded(removeUser, appId);
schedulePackageCleaning(packageName, removeUser, false);
synchronized (mPackages) {
@@ -12135,7 +12139,7 @@
// Always delete data directories for package, even if we found no other
// record of app. This helps users recover from UID mismatches without
// resorting to a full data wipe.
- int retCode = mInstaller.clearUserData(packageName, userId);
+ int retCode = mInstaller.clearUserData(pkg.volumeUuid, packageName, userId);
if (retCode < 0) {
Slog.w(TAG, "Couldn't remove cache files for package: " + packageName);
return false;
@@ -12156,7 +12160,8 @@
if (pkg != null && pkg.applicationInfo.primaryCpuAbi != null &&
!VMRuntime.is64BitAbi(pkg.applicationInfo.primaryCpuAbi)) {
final String nativeLibPath = pkg.applicationInfo.nativeLibraryDir;
- if (mInstaller.linkNativeLibraryDirectory(pkg.packageName, nativeLibPath, userId) < 0) {
+ if (mInstaller.linkNativeLibraryDirectory(pkg.volumeUuid, pkg.packageName,
+ nativeLibPath, userId) < 0) {
Slog.w(TAG, "Failed linking native library dir");
return false;
}
@@ -12232,7 +12237,7 @@
Slog.w(TAG, "Package " + packageName + " has no applicationInfo.");
return false;
}
- int retCode = mInstaller.deleteCacheFiles(packageName, userId);
+ int retCode = mInstaller.deleteCacheFiles(p.volumeUuid, packageName, userId);
if (retCode < 0) {
Slog.w(TAG, "Couldn't remove cache files for package: "
+ packageName + " u" + userId);
@@ -12310,8 +12315,8 @@
// TODO(multiArch): Extend getSizeInfo to look at *all* instruction sets, not
// just the primary.
String[] dexCodeInstructionSets = getDexCodeInstructionSets(getAppDexInstructionSets(ps));
- int res = mInstaller.getSizeInfo(packageName, userHandle, p.baseCodePath, libDirRoot,
- publicSrcDir, asecPath, dexCodeInstructionSets, pStats);
+ int res = mInstaller.getSizeInfo(p.volumeUuid, packageName, userHandle, p.baseCodePath,
+ libDirRoot, publicSrcDir, asecPath, dexCodeInstructionSets, pStats);
if (res < 0) {
return false;
}
@@ -14305,7 +14310,15 @@
// Technically, we shouldn't be doing this with the package lock
// held. However, this is very rare, and there is already so much
// other disk I/O going on, that we'll let it slide for now.
- mInstaller.removeUserDataDirs(userHandle);
+ final StorageManager storage = StorageManager.from(mContext);
+ final List<VolumeInfo> vols = storage.getVolumes();
+ for (VolumeInfo vol : vols) {
+ if (vol.getType() == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) {
+ final String volumeUuid = vol.getFsUuid();
+ Slog.d(TAG, "Removing user data on volume " + volumeUuid);
+ mInstaller.removeUserDataDirs(volumeUuid, userHandle);
+ }
+ }
}
mUserNeedsBadging.delete(userHandle);
removeUnusedPackagesLILPw(userManager, userHandle);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index d2964bb..f3fdb0d 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -207,6 +207,8 @@
private static int mFirstAvailableUid = 0;
+ // TODO: store SDK versions and fingerprint for each volume UUID
+
// These are the last platform API version we were using for
// the apps installed on internal and external storage. It is
// used to grant newer permissions one time during a system upgrade.
@@ -3437,7 +3439,7 @@
// Only system apps are initially installed.
ps.setInstalled((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0, userHandle);
// Need to create a data directory for all apps under this user.
- installer.createUserData(ps.name,
+ installer.createUserData(ps.volumeUuid, ps.name,
UserHandle.getUid(userHandle, ps.appId), userHandle,
ps.pkg.applicationInfo.seinfo);
}
diff --git a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
index 2673557..cbbcb0e 100644
--- a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
+++ b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
@@ -74,6 +74,8 @@
public class DeviceStorageMonitorService extends SystemService {
static final String TAG = "DeviceStorageMonitorService";
+ // TODO: extend to watch and manage caches on all private volumes
+
static final boolean DEBUG = false;
static final boolean localLOGV = false;
@@ -220,7 +222,7 @@
try {
if (localLOGV) Slog.i(TAG, "Clearing cache");
IPackageManager.Stub.asInterface(ServiceManager.getService("package")).
- freeStorageAndNotify(mMemCacheTrimToThreshold, mClearCacheObserver);
+ freeStorageAndNotify(null, mMemCacheTrimToThreshold, mClearCacheObserver);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
mClearingCache = false;
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 5a8c7ff..276b713 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -535,21 +535,16 @@
throw new UnsupportedOperationException();
}
- /**
- * @hide - to match hiding in superclass
- */
+ /** {@hide} */
@Override
- public void freeStorageAndNotify(
- long idealStorageSize, IPackageDataObserver observer) {
+ public void freeStorageAndNotify(String volumeUuid, long idealStorageSize,
+ IPackageDataObserver observer) {
throw new UnsupportedOperationException();
}
- /**
- * @hide - to match hiding in superclass
- */
+ /** {@hide} */
@Override
- public void freeStorage(
- long idealStorageSize, IntentSender pi) {
+ public void freeStorage(String volumeUuid, long idealStorageSize, IntentSender pi) {
throw new UnsupportedOperationException();
}