/* * 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; } }