Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7f0c721f authored by Yohei Yukawa's avatar Yohei Yukawa
Browse files

Introduce IInputMethodManagerGlobal

In reality methods defined in IInputMethodManager are no more or less
than a set of global methods, because you can write the following
logic anywhere.

  var service = IInputMethodManager.Stub.asInterface(
          ServiceManager.getService(Context.INPUT_METHOD_SERVICE));
  if (service != null) {
      try {
          service.doSomething();
      } catch (RemoteException e) {
          e.rethrowFromSystemServer();
      }
  }

With above observation, this CL introduces IInputMethodManagerGlobal
as a collection of static methods that invoke methods defined in
IInputMethodManager so that other framework classes do not need to
write the same piece of code.

  public final class IInputMethodManagerGlobal {

      public static void doSomething() {
          final var service = getService();
          if (service != null) {
              try {
                  service.doSomething();
              } catch (RemoteException e) {
                  e.rethrowFromSystemServer();
              }
          }
      }

      ......

  }

This CL then simplifies ImeTracing to demonstrate how
IInputMethodManagerGlobal can be used.

Basically this is a mechanical refactoring.  There should be no
observable behavior change.

Bug: 234882948
Test: presubmit
Change-Id: I5412eb1d44e3d515ca955f00a2e777b659a15b14
parent e16dbabf
Loading
Loading
Loading
Loading
+158 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.internal.inputmethod;

import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresNoPermission;
import android.annotation.RequiresPermission;
import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;

import com.android.internal.view.IInputMethodManager;

import java.util.function.Consumer;

/**
 * A global wrapper to directly invoke {@link IInputMethodManager} IPCs.
 *
 * <p>All public static methods are guaranteed to be thread-safe.</p>
 *
 * <p>All public methods are guaranteed to do nothing when {@link IInputMethodManager} is
 * unavailable.</p>
 */
public final class IInputMethodManagerGlobal {
    @Nullable
    private static volatile IInputMethodManager sServiceCache = null;

    /**
     * @return {@code true} if {@link IInputMethodManager} is available.
     */
    @AnyThread
    public static boolean isAvailable() {
        return getService() != null;
    }

    @AnyThread
    @Nullable
    private static IInputMethodManager getService() {
        IInputMethodManager service = sServiceCache;
        if (service == null) {
            service = IInputMethodManager.Stub.asInterface(
                    ServiceManager.getService(Context.INPUT_METHOD_SERVICE));
            if (service == null) {
                return null;
            }
            sServiceCache = service;
        }
        return service;
    }

    @AnyThread
    private static void handleRemoteExceptionOrRethrow(@NonNull RemoteException e,
            @Nullable Consumer<RemoteException> exceptionHandler) {
        if (exceptionHandler != null) {
            exceptionHandler.accept(e);
        } else {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Invokes {@link IInputMethodManager#startProtoDump(byte[], int, String)}.
     *
     * @param protoDump client or service side information to be stored by the server
     * @param source where the information is coming from, refer to
     *               {@link ImeTracing#IME_TRACING_FROM_CLIENT} and
     *               {@link ImeTracing#IME_TRACING_FROM_IMS}
     * @param where where the information is coming from.
     * @param exceptionHandler an optional {@link RemoteException} handler.
     */
    @RequiresNoPermission
    @AnyThread
    public static void startProtoDump(byte[] protoDump, int source, String where,
            @Nullable Consumer<RemoteException> exceptionHandler) {
        final IInputMethodManager service = getService();
        if (service == null) {
            return;
        }
        try {
            service.startProtoDump(protoDump, source, where);
        } catch (RemoteException e) {
            handleRemoteExceptionOrRethrow(e, exceptionHandler);
        }
    }

    /**
     * Invokes {@link IInputMethodManager#startImeTrace()}.
     *
     * @param exceptionHandler an optional {@link RemoteException} handler.
     */
    @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
    @AnyThread
    public static void startImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
        final IInputMethodManager service = getService();
        if (service == null) {
            return;
        }
        try {
            service.startImeTrace();
        } catch (RemoteException e) {
            handleRemoteExceptionOrRethrow(e, exceptionHandler);
        }
    }

    /**
     * Invokes {@link IInputMethodManager#stopImeTrace()}.
     *
     * @param exceptionHandler an optional {@link RemoteException} handler.
     */
    @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
    @AnyThread
    public static void stopImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
        final IInputMethodManager service = getService();
        if (service == null) {
            return;
        }
        try {
            service.stopImeTrace();
        } catch (RemoteException e) {
            handleRemoteExceptionOrRethrow(e, exceptionHandler);
        }
    }

    /**
     * Invokes {@link IInputMethodManager#isImeTraceEnabled()}.
     *
     * @return The return value of {@link IInputMethodManager#isImeTraceEnabled()}.
     */
    @RequiresNoPermission
    @AnyThread
    public static boolean isImeTraceEnabled() {
        final IInputMethodManager service = getService();
        if (service == null) {
            return false;
        }
        try {
            return service.isImeTraceEnabled();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}
+18 −32
Original line number Diff line number Diff line
@@ -19,16 +19,10 @@ package com.android.internal.inputmethod;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityThread;
import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import android.view.inputmethod.InputMethodManager;

import com.android.internal.view.IInputMethodManager;

import java.io.PrintWriter;

/**
@@ -49,16 +43,12 @@ public abstract class ImeTracing {

    private static ImeTracing sInstance;
    static boolean sEnabled = false;
    IInputMethodManager mService;

    private final boolean mIsAvailable = IInputMethodManagerGlobal.isAvailable();

    protected boolean mDumpInProgress;
    protected final Object mDumpInProgressLock = new Object();

    ImeTracing() throws ServiceNotFoundException {
        mService = IInputMethodManager.Stub.asInterface(
                ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE));
    }

    /**
     * Returns an instance of {@link ImeTracingServerImpl} when called from a server side class
     * and an instance of {@link ImeTracingClientImpl} when called from a client side class.
@@ -68,11 +58,14 @@ public abstract class ImeTracing {
     */
    public static ImeTracing getInstance() {
        if (sInstance == null) {
            if (isSystemProcess()) {
                sInstance = new ImeTracingServerImpl();
            } else {
                try {
                sInstance = isSystemProcess()
                        ? new ImeTracingServerImpl() : new ImeTracingClientImpl();
            } catch (RemoteException | ServiceNotFoundException e) {
                Log.e(TAG, "Exception while creating ImeTracing instance", e);
                    sInstance = new ImeTracingClientImpl();
                } catch (RuntimeException e) {
                    Log.e(TAG, "Exception while creating ImeTracingClientImpl instance", e);
                }
            }
        }
        return sInstance;
@@ -87,32 +80,25 @@ public abstract class ImeTracing {
     * and {@see #IME_TRACING_FROM_IMS}
     * @param where
     */
    public void sendToService(byte[] protoDump, int source, String where) throws RemoteException {
        mService.startProtoDump(protoDump, source, where);
    public void sendToService(byte[] protoDump, int source, String where) {
        IInputMethodManagerGlobal.startProtoDump(protoDump, source, where,
                e -> Log.e(TAG, "Exception while sending ime-related dump to server", e));
    }

    /**
     * Calling {@link IInputMethodManager#startImeTrace()}} to capture IME trace.
     * Start IME trace.
     */
    @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
    public final void startImeTrace() {
        try {
            mService.startImeTrace();
        } catch (RemoteException e) {
            Log.e(TAG, "Could not start ime trace." + e);
        }
        IInputMethodManagerGlobal.startImeTrace(e -> Log.e(TAG, "Could not start ime trace.", e));
    }

    /**
     * Calling {@link IInputMethodManager#stopImeTrace()} to stop IME trace.
     * Stop IME trace.
     */
    @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
    public final void stopImeTrace() {
        try {
            mService.stopImeTrace();
        } catch (RemoteException e) {
            Log.e(TAG, "Could not stop ime trace." + e);
        }
        IInputMethodManagerGlobal.stopImeTrace(e -> Log.e(TAG, "Could not stop ime trace.", e));
    }

    /**
@@ -193,7 +179,7 @@ public abstract class ImeTracing {
     * @return {@code true} if tracing is available, {@code false} otherwise.
     */
    public boolean isAvailable() {
        return mService != null;
        return mIsAvailable;
    }

    /**
+2 −9
Original line number Diff line number Diff line
@@ -18,9 +18,6 @@ package com.android.internal.inputmethod;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.RemoteException;
import android.os.ServiceManager.ServiceNotFoundException;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import android.view.inputmethod.InputMethodManager;

@@ -30,8 +27,8 @@ import java.io.PrintWriter;
 * An implementation of {@link ImeTracing} for non system_server processes.
 */
class ImeTracingClientImpl extends ImeTracing {
    ImeTracingClientImpl() throws ServiceNotFoundException, RemoteException {
        sEnabled = mService.isImeTraceEnabled();
    ImeTracingClientImpl() {
        sEnabled = IInputMethodManagerGlobal.isImeTraceEnabled();
    }

    @Override
@@ -56,8 +53,6 @@ class ImeTracingClientImpl extends ImeTracing {
            ProtoOutputStream proto = new ProtoOutputStream();
            immInstance.dumpDebug(proto, icProto);
            sendToService(proto.getBytes(), IME_TRACING_FROM_CLIENT, where);
        } catch (RemoteException e) {
            Log.e(TAG, "Exception while sending ime-related client dump to server", e);
        } finally {
            mDumpInProgress = false;
        }
@@ -81,8 +76,6 @@ class ImeTracingClientImpl extends ImeTracing {
            ProtoOutputStream proto = new ProtoOutputStream();
            dumper.dumpToProto(proto, icProto);
            sendToService(proto.getBytes(), IME_TRACING_FROM_IMS, where);
        } catch (RemoteException e) {
            Log.e(TAG, "Exception while sending ime-related service dump to server", e);
        } finally {
            mDumpInProgress = false;
        }
+1 −5
Original line number Diff line number Diff line
@@ -19,8 +19,6 @@ package com.android.internal.inputmethod;
import static android.os.Build.IS_USER;

import android.annotation.Nullable;
import android.os.RemoteException;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.SystemClock;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
@@ -71,7 +69,7 @@ class ImeTracingServerImpl extends ImeTracing {

    private final Object mEnabledLock = new Object();

    ImeTracingServerImpl() throws ServiceNotFoundException {
    ImeTracingServerImpl() {
        mBufferClients = new TraceBuffer<>(BUFFER_CAPACITY);
        mTraceFileClients = new File(TRACE_DIRNAME + TRACE_FILENAME_CLIENTS);
        mBufferIms = new TraceBuffer<>(BUFFER_CAPACITY);
@@ -132,8 +130,6 @@ class ImeTracingServerImpl extends ImeTracing {

        try {
            sendToService(null, IME_TRACING_FROM_IMMS, where);
        } catch (RemoteException e) {
            Log.e(TAG, "Exception while sending ime-related manager service dump to server", e);
        } finally {
            mDumpInProgress = false;
        }
+1 −0
Original line number Diff line number Diff line
@@ -123,6 +123,7 @@ interface IInputMethodManager {
    @JavaPassthrough(annotation="@android.annotation.RequiresNoPermission")
    void startProtoDump(in byte[] protoDump, int source, String where);

    @JavaPassthrough(annotation="@android.annotation.RequiresNoPermission")
    boolean isImeTraceEnabled();

    // Starts an ime trace.