content: /*
   * This file is part of LSPosed.
   *
   * LSPosed is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   *
   * LSPosed is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with LSPosed.  If not, see <https://www.gnu.org/licenses/>.
   *
   * Copyright (C) 2021 - 2022 LSPosed Contributors
   */
  
  package org.lsposed.lspd.service;
  
  import static org.lsposed.lspd.service.ServiceManager.TAG;
  import static org.lsposed.lspd.service.ServiceManager.getSystemServiceManager;
  
  import android.os.Build;
  import android.os.IBinder;
  import android.os.IServiceCallback;
  import android.os.Parcel;
  import android.os.RemoteException;
  import android.os.SystemProperties;
  import android.util.Log;
  
  public class LSPSystemServerService extends ILSPSystemServerService.Stub implements IBinder.DeathRecipient {
  
      public static final String PROXY_SERVICE_NAME = "serial";
  
      private IBinder originService = null;
      private int requested;
  
      public boolean systemServerRequested() {
          return requested > 0;
      }
  
      public void putBinderForSystemServer() {
          android.os.ServiceManager.addService(PROXY_SERVICE_NAME, this);
          binderDied();
      }
  
      public LSPSystemServerService(int maxRetry) {
          Log.d(TAG, "LSPSystemServerService::LSPSystemServerService");
          requested = -maxRetry;
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
              // Registers a callback when system is registering an authentic "serial" service
              // And we are proxying all requests to that system service
              var serviceCallback = new IServiceCallback.Stub() {
                  @Override
                  public void onRegistration(String name, IBinder binder) {
                      Log.d(TAG, "LSPSystemServerService::LSPSystemServerService onRegistration: " + name + " " + binder);
                      if (name.equals(PROXY_SERVICE_NAME) && binder != null && binder != LSPSystemServerService.this) {
                          Log.d(TAG, "Register " + name + " " + binder);
                          originService = binder;
                          LSPSystemServerService.this.linkToDeath();
                      }
                  }
  
                  @Override
                  public IBinder asBinder() {
                      return this;
                  }
              };
              try {
                  getSystemServiceManager().registerForNotifications(PROXY_SERVICE_NAME, serviceCallback);
              } catch (Throwable e) {
                  Log.e(TAG, "unregister: ", e);
              }
          }
      }
  
      @Override
      public ILSPApplicationService requestApplicationService(int uid, int pid, String processName, IBinder heartBeat) {
          Log.d(TAG, "ILSPApplicationService.requestApplicationService: " + uid + " " + pid + " " + processName + " " + heartBeat);
          requested = 1;
          if (ConfigManager.getInstance().shouldSkipSystemServer() || uid != 1000 || heartBeat == null || !"system".equals(processName))
              return null;
          else
              return ServiceManager.requestApplicationService(uid, pid, processName, heartBeat);
      }
  
      @Override
      public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
          Log.d(TAG, "LSPSystemServerService.onTransact: code=" + code);
          if (originService != null) {
              return originService.transact(code, data, reply, flags);
          }
  
          switch (code) {
              case BridgeService.TRANSACTION_CODE -> {
                  int uid = data.readInt();
                  int pid = data.readInt();
                  String processName = data.readString();
                  IBinder heartBeat = data.readStrongBinder();
                  var service = requestApplicationService(uid, pid, processName, heartBeat);
                  if (service != null) {
                      Log.d(TAG, "LSPSystemServerService.onTransact requestApplicationService granted: " + service);
                      reply.writeNoException();
                      reply.writeStrongBinder(service.asBinder());
                      return true;
                  } else {
                      Log.d(TAG, "LSPSystemServerService.onTransact requestApplicationService rejected");
                      return false;
                  }
              }
              case LSPApplicationService.OBFUSCATION_MAP_TRANSACTION_CODE, LSPApplicationService.DEX_TRANSACTION_CODE -> {
                  // Proxy LSP dex transaction to Application Binder
                  return ServiceManager.getApplicationService().onTransact(code, data, reply, flags);
              }
              default -> {
                  return super.onTransact(code, data, reply, flags);
              }
          }
      }
  
      public void linkToDeath() {
          try {
              originService.linkToDeath(this, 0);
          } catch (Throwable e) {
              Log.e(TAG, "system server service: link to death", e);
          }
      }
  
      @Override
      public void binderDied() {
          if (originService != null) {
              originService.unlinkToDeath(this, 0);
              originService = null;
          }
      }
  
      public void maybeRetryInject() {
          if (requested < 0) {
              Log.w(TAG, "System server injection fails, trying a restart");
              ++requested;
              if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && Build.SUPPORTED_32_BIT_ABIS.length > 0) {
                  // Only devices with both 32-bit and 64-bit support have zygote_secondary
                  SystemProperties.set("ctl.restart", "zygote_secondary");
              } else {
                  SystemProperties.set("ctl.restart", "zygote");
              }
          }
      }
  }
  