/*
|
* Copyright (C) 2019 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;
|
|
import android.content.Context;
|
import android.content.pm.PackageManager;
|
import android.gsi.GsiInstallParams;
|
import android.gsi.GsiProgress;
|
import android.gsi.IGsiService;
|
import android.os.Environment;
|
import android.os.IBinder;
|
import android.os.IBinder.DeathRecipient;
|
import android.os.RemoteException;
|
import android.os.ServiceManager;
|
import android.os.SystemProperties;
|
import android.os.UserHandle;
|
import android.os.image.IDynamicSystemService;
|
import android.os.storage.StorageManager;
|
import android.os.storage.StorageVolume;
|
import android.util.Slog;
|
|
import java.io.File;
|
|
/**
|
* DynamicSystemService implements IDynamicSystemService. It provides permission check before
|
* passing requests to gsid
|
*/
|
public class DynamicSystemService extends IDynamicSystemService.Stub implements DeathRecipient {
|
private static final String TAG = "DynamicSystemService";
|
private static final String NO_SERVICE_ERROR = "no gsiservice";
|
private static final int GSID_ROUGH_TIMEOUT_MS = 8192;
|
private static final String PATH_DEFAULT = "/data/gsi";
|
private Context mContext;
|
private volatile IGsiService mGsiService;
|
|
DynamicSystemService(Context context) {
|
mContext = context;
|
}
|
|
private static IGsiService connect(DeathRecipient recipient) throws RemoteException {
|
IBinder binder = ServiceManager.getService("gsiservice");
|
if (binder == null) {
|
return null;
|
}
|
/**
|
* The init will restart gsiservice if it crashed and the proxy object will need to be
|
* re-initialized in this case.
|
*/
|
binder.linkToDeath(recipient, 0);
|
return IGsiService.Stub.asInterface(binder);
|
}
|
|
/** implements DeathRecipient */
|
@Override
|
public void binderDied() {
|
Slog.w(TAG, "gsiservice died; reconnecting");
|
synchronized (this) {
|
mGsiService = null;
|
}
|
}
|
|
private IGsiService getGsiService() throws RemoteException {
|
checkPermission();
|
|
if (!"running".equals(SystemProperties.get("init.svc.gsid"))) {
|
SystemProperties.set("ctl.start", "gsid");
|
}
|
|
for (int sleepMs = 64; sleepMs <= (GSID_ROUGH_TIMEOUT_MS << 1); sleepMs <<= 1) {
|
synchronized (this) {
|
if (mGsiService == null) {
|
mGsiService = connect(this);
|
}
|
if (mGsiService != null) {
|
return mGsiService;
|
}
|
}
|
|
try {
|
Slog.d(TAG, "GsiService is not ready, wait for " + sleepMs + "ms");
|
Thread.sleep(sleepMs);
|
} catch (InterruptedException e) {
|
Slog.e(TAG, "Interrupted when waiting for GSID");
|
return null;
|
}
|
}
|
|
throw new RemoteException(NO_SERVICE_ERROR);
|
}
|
|
private void checkPermission() {
|
if (mContext.checkCallingOrSelfPermission(
|
android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
|
!= PackageManager.PERMISSION_GRANTED) {
|
throw new SecurityException("Requires MANAGE_DYNAMIC_SYSTEM permission");
|
}
|
}
|
|
@Override
|
public boolean startInstallation(long systemSize, long userdataSize) throws RemoteException {
|
// priority from high to low: sysprop -> sdcard -> /data
|
String path = SystemProperties.get("os.aot.path");
|
if (path.isEmpty()) {
|
final int userId = UserHandle.myUserId();
|
final StorageVolume[] volumes =
|
StorageManager.getVolumeList(userId, StorageManager.FLAG_FOR_WRITE);
|
for (StorageVolume volume : volumes) {
|
if (volume.isEmulated()) continue;
|
if (!volume.isRemovable()) continue;
|
if (!Environment.MEDIA_MOUNTED.equals(volume.getState())) continue;
|
File sdCard = volume.getPathFile();
|
if (sdCard.isDirectory()) {
|
/**
|
* If sdcard is visible, it's path starts with "/storage/" and internal path
|
* starts with "/mnt/media_rw". gsi service only take path that start with
|
* "/mnt/media_rw" as external storage path, so we use the internal path here.
|
*/
|
path = volume.getInternalPath();
|
break;
|
}
|
}
|
if (path.isEmpty()) {
|
path = PATH_DEFAULT;
|
}
|
Slog.i(TAG, "startInstallation -> " + path);
|
}
|
GsiInstallParams installParams = new GsiInstallParams();
|
installParams.installDir = path;
|
installParams.gsiSize = systemSize;
|
installParams.userdataSize = userdataSize;
|
return getGsiService().beginGsiInstall(installParams) == 0;
|
}
|
|
@Override
|
public GsiProgress getInstallationProgress() throws RemoteException {
|
return getGsiService().getInstallProgress();
|
}
|
|
@Override
|
public boolean abort() throws RemoteException {
|
return getGsiService().cancelGsiInstall();
|
}
|
|
@Override
|
public boolean isInUse() throws RemoteException {
|
boolean gsidWasRunning = "running".equals(SystemProperties.get("init.svc.gsid"));
|
boolean isInUse = false;
|
|
try {
|
isInUse = getGsiService().isGsiRunning();
|
} finally {
|
if (!gsidWasRunning && !isInUse) {
|
SystemProperties.set("ctl.stop", "gsid");
|
}
|
}
|
|
return isInUse;
|
}
|
|
@Override
|
public boolean isInstalled() throws RemoteException {
|
return getGsiService().isGsiInstalled();
|
}
|
|
@Override
|
public boolean isEnabled() throws RemoteException {
|
return getGsiService().isGsiEnabled();
|
}
|
|
@Override
|
public boolean remove() throws RemoteException {
|
return getGsiService().removeGsiInstall();
|
}
|
|
@Override
|
public boolean setEnable(boolean enable) throws RemoteException {
|
IGsiService gsiService = getGsiService();
|
if (enable) {
|
final int status = gsiService.getGsiBootStatus();
|
final boolean singleBoot = (status == IGsiService.BOOT_STATUS_SINGLE_BOOT);
|
return gsiService.setGsiBootable(singleBoot) == 0;
|
} else {
|
return gsiService.disableGsiInstall();
|
}
|
}
|
|
@Override
|
public boolean write(byte[] buf) throws RemoteException {
|
return getGsiService().commitGsiChunkFromMemory(buf);
|
}
|
|
@Override
|
public boolean commit() throws RemoteException {
|
return getGsiService().setGsiBootable(true) == 0;
|
}
|
}
|