/*
|
* Copyright 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.gpu;
|
|
import static android.content.Intent.ACTION_PACKAGE_ADDED;
|
import static android.content.Intent.ACTION_PACKAGE_CHANGED;
|
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
|
|
import android.annotation.NonNull;
|
import android.content.BroadcastReceiver;
|
import android.content.ContentResolver;
|
import android.content.Context;
|
import android.content.Intent;
|
import android.content.IntentFilter;
|
import android.content.pm.ApplicationInfo;
|
import android.content.pm.PackageManager;
|
import android.database.ContentObserver;
|
import android.gamedriver.GameDriverProto.Blacklist;
|
import android.gamedriver.GameDriverProto.Blacklists;
|
import android.net.Uri;
|
import android.os.Build;
|
import android.os.Handler;
|
import android.os.SystemProperties;
|
import android.os.UserHandle;
|
import android.provider.DeviceConfig;
|
import android.provider.DeviceConfig.Properties;
|
import android.provider.Settings;
|
import android.util.Base64;
|
import android.util.Slog;
|
|
import com.android.framework.protobuf.InvalidProtocolBufferException;
|
import com.android.internal.annotations.GuardedBy;
|
import com.android.server.SystemService;
|
|
import java.io.BufferedReader;
|
import java.io.IOException;
|
import java.io.InputStreamReader;
|
import java.util.ArrayList;
|
import java.util.List;
|
|
/**
|
* Service to manage GPU related features.
|
*
|
* <p>GPU service is a core service that monitors, coordinates all GPU related features,
|
* as well as collect metrics about the GPU and GPU driver.</p>
|
*/
|
public class GpuService extends SystemService {
|
public static final String TAG = "GpuService";
|
public static final boolean DEBUG = false;
|
|
private static final String PROPERTY_GFX_DRIVER = "ro.gfx.driver.0";
|
private static final String GAME_DRIVER_WHITELIST_FILENAME = "whitelist.txt";
|
private static final int BASE64_FLAGS = Base64.NO_PADDING | Base64.NO_WRAP;
|
|
private final Context mContext;
|
private final String mDriverPackageName;
|
private final PackageManager mPackageManager;
|
private final Object mLock = new Object();
|
private final Object mDeviceConfigLock = new Object();
|
private ContentResolver mContentResolver;
|
private long mGameDriverVersionCode;
|
private SettingsObserver mSettingsObserver;
|
private DeviceConfigListener mDeviceConfigListener;
|
@GuardedBy("mLock")
|
private Blacklists mBlacklists;
|
|
public GpuService(Context context) {
|
super(context);
|
|
mContext = context;
|
mDriverPackageName = SystemProperties.get(PROPERTY_GFX_DRIVER);
|
mGameDriverVersionCode = -1;
|
mPackageManager = context.getPackageManager();
|
if (mDriverPackageName != null && !mDriverPackageName.isEmpty()) {
|
final IntentFilter packageFilter = new IntentFilter();
|
packageFilter.addAction(ACTION_PACKAGE_ADDED);
|
packageFilter.addAction(ACTION_PACKAGE_CHANGED);
|
packageFilter.addAction(ACTION_PACKAGE_REMOVED);
|
packageFilter.addDataScheme("package");
|
getContext().registerReceiverAsUser(new PackageReceiver(), UserHandle.ALL,
|
packageFilter, null, null);
|
}
|
}
|
|
@Override
|
public void onStart() {
|
}
|
|
@Override
|
public void onBootPhase(int phase) {
|
if (phase == PHASE_BOOT_COMPLETED) {
|
mContentResolver = mContext.getContentResolver();
|
if (mDriverPackageName == null || mDriverPackageName.isEmpty()) {
|
return;
|
}
|
mSettingsObserver = new SettingsObserver();
|
mDeviceConfigListener = new DeviceConfigListener();
|
fetchGameDriverPackageProperties();
|
processBlacklists();
|
setBlacklist();
|
}
|
}
|
|
private final class SettingsObserver extends ContentObserver {
|
private final Uri mGameDriverBlackUri =
|
Settings.Global.getUriFor(Settings.Global.GAME_DRIVER_BLACKLISTS);
|
|
SettingsObserver() {
|
super(new Handler());
|
mContentResolver.registerContentObserver(mGameDriverBlackUri, false, this,
|
UserHandle.USER_ALL);
|
}
|
|
@Override
|
public void onChange(boolean selfChange, Uri uri) {
|
if (uri == null) {
|
return;
|
}
|
|
if (mGameDriverBlackUri.equals(uri)) {
|
processBlacklists();
|
setBlacklist();
|
}
|
}
|
}
|
|
private final class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener {
|
|
DeviceConfigListener() {
|
super();
|
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_GAME_DRIVER,
|
mContext.getMainExecutor(), this);
|
}
|
@Override
|
public void onPropertiesChanged(Properties properties) {
|
synchronized (mDeviceConfigLock) {
|
if (properties.getKeyset().contains(Settings.Global.GAME_DRIVER_BLACKLISTS)) {
|
parseBlacklists(
|
properties.getString(Settings.Global.GAME_DRIVER_BLACKLISTS, ""));
|
setBlacklist();
|
}
|
}
|
}
|
}
|
|
private final class PackageReceiver extends BroadcastReceiver {
|
@Override
|
public void onReceive(@NonNull final Context context, @NonNull final Intent intent) {
|
final Uri data = intent.getData();
|
if (data == null && DEBUG) {
|
Slog.e(TAG, "Cannot handle package broadcast with null data");
|
return;
|
}
|
final String packageName = data.getSchemeSpecificPart();
|
if (!packageName.equals(mDriverPackageName)) {
|
return;
|
}
|
|
final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
|
|
switch (intent.getAction()) {
|
case ACTION_PACKAGE_ADDED:
|
case ACTION_PACKAGE_CHANGED:
|
case ACTION_PACKAGE_REMOVED:
|
fetchGameDriverPackageProperties();
|
setBlacklist();
|
break;
|
default:
|
// do nothing
|
break;
|
}
|
}
|
}
|
|
private static void assetToSettingsGlobal(Context context, Context driverContext,
|
String fileName, String settingsGlobal, CharSequence delimiter) {
|
try {
|
final BufferedReader reader = new BufferedReader(
|
new InputStreamReader(driverContext.getAssets().open(fileName)));
|
final ArrayList<String> assetStrings = new ArrayList<>();
|
for (String assetString; (assetString = reader.readLine()) != null; ) {
|
assetStrings.add(assetString);
|
}
|
Settings.Global.putString(context.getContentResolver(),
|
settingsGlobal,
|
String.join(delimiter, assetStrings));
|
} catch (IOException e) {
|
if (DEBUG) {
|
Slog.w(TAG, "Failed to load " + fileName + ", abort.");
|
}
|
}
|
}
|
|
private void fetchGameDriverPackageProperties() {
|
final ApplicationInfo driverInfo;
|
try {
|
driverInfo = mPackageManager.getApplicationInfo(mDriverPackageName,
|
PackageManager.MATCH_SYSTEM_ONLY);
|
} catch (PackageManager.NameNotFoundException e) {
|
if (DEBUG) {
|
Slog.e(TAG, "driver package '" + mDriverPackageName + "' not installed");
|
}
|
return;
|
}
|
|
// O drivers are restricted to the sphal linker namespace, so don't try to use
|
// packages unless they declare they're compatible with that restriction.
|
if (driverInfo.targetSdkVersion < Build.VERSION_CODES.O) {
|
if (DEBUG) {
|
Slog.w(TAG, "Driver package is not known to be compatible with O");
|
}
|
return;
|
}
|
|
// Reset the whitelist.
|
Settings.Global.putString(mContentResolver,
|
Settings.Global.GAME_DRIVER_WHITELIST, "");
|
mGameDriverVersionCode = driverInfo.longVersionCode;
|
|
try {
|
final Context driverContext = mContext.createPackageContext(mDriverPackageName,
|
Context.CONTEXT_RESTRICTED);
|
|
assetToSettingsGlobal(mContext, driverContext, GAME_DRIVER_WHITELIST_FILENAME,
|
Settings.Global.GAME_DRIVER_WHITELIST, ",");
|
} catch (PackageManager.NameNotFoundException e) {
|
if (DEBUG) {
|
Slog.w(TAG, "driver package '" + mDriverPackageName + "' not installed");
|
}
|
}
|
}
|
|
private void processBlacklists() {
|
String base64String = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_GAME_DRIVER,
|
Settings.Global.GAME_DRIVER_BLACKLISTS);
|
if (base64String == null) {
|
base64String =
|
Settings.Global.getString(mContentResolver,
|
Settings.Global.GAME_DRIVER_BLACKLISTS);
|
}
|
parseBlacklists(base64String != null ? base64String : "");
|
}
|
|
private void parseBlacklists(String base64String) {
|
synchronized (mLock) {
|
// Reset all blacklists
|
mBlacklists = null;
|
try {
|
mBlacklists = Blacklists.parseFrom(Base64.decode(base64String, BASE64_FLAGS));
|
} catch (IllegalArgumentException e) {
|
if (DEBUG) {
|
Slog.w(TAG, "Can't parse blacklist, skip and continue...");
|
}
|
} catch (InvalidProtocolBufferException e) {
|
if (DEBUG) {
|
Slog.w(TAG, "Can't parse blacklist, skip and continue...");
|
}
|
}
|
}
|
}
|
|
private void setBlacklist() {
|
Settings.Global.putString(mContentResolver,
|
Settings.Global.GAME_DRIVER_BLACKLIST, "");
|
synchronized (mLock) {
|
if (mBlacklists == null) {
|
return;
|
}
|
List<Blacklist> blacklists = mBlacklists.getBlacklistsList();
|
for (Blacklist blacklist : blacklists) {
|
if (blacklist.getVersionCode() == mGameDriverVersionCode) {
|
Settings.Global.putString(mContentResolver,
|
Settings.Global.GAME_DRIVER_BLACKLIST,
|
String.join(",", blacklist.getPackageNamesList()));
|
return;
|
}
|
}
|
}
|
}
|
}
|