content: /*
   * Copyright (C) 2016 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 Lice`nse 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 androidx.test.runner.permission;
  
  import static androidx.test.internal.util.Checks.checkNotNull;
  
  import android.annotation.TargetApi;
  import android.app.UiAutomation;
  import android.os.ParcelFileDescriptor;
  import android.util.Log;
  import androidx.annotation.VisibleForTesting;
  import androidx.test.InstrumentationRegistry;
  import java.io.BufferedReader;
  import java.io.IOException;
  import java.io.InputStreamReader;
  import java.util.concurrent.TimeUnit;
  import java.util.concurrent.TimeoutException;
  
  /**
   * {@link ShellCommand} implementation which uses {@link UiAutomation} to grant a permission at
   * runtime.
   */
  @TargetApi(value = 23)
  class UiAutomationShellCommand extends ShellCommand {
  
    private static final String TAG = "UiAutomationShellCmd";
  
    enum PmCommand {
      GRANT_PERMISSION("grant");
  
      private final String pmCommand;
  
      PmCommand(String command) {
        pmCommand = "pm " + command;
      }
  
      public String get() {
        return pmCommand;
      }
    }
  
    private final String targetPackage;
    private final String permission;
    private final PmCommand command;
    private final UiAutomation uiAutomation;
  
    UiAutomationShellCommand(String targetPackage, String permission, PmCommand pmCommand) {
      this.targetPackage = shellEscape(targetPackage);
      this.permission = shellEscape(permission);
      command = pmCommand;
      uiAutomation = checkNotNull(InstrumentationRegistry.getInstrumentation().getUiAutomation());
    }
  
    @Override
    public void execute() throws Exception {
      executePermissionCommand(commandForPermission());
    }
  
    @VisibleForTesting
    protected String commandForPermission() {
      return new StringBuilder(command.get())
          .append(" ")
          .append(targetPackage)
          .append(" ")
          .append(permission)
          .toString();
    }
  
    /**
     * Since {@link UiAutomation#executeShellCommand(String)} does not block until the command
     * executed successfully. Write the output to byte array to make sure permission is granted before
     * starting the test.
     *
     * @param cmd to run to request permissions
     */
    private void executePermissionCommand(String cmd) throws IOException {
      Log.i(TAG, "Requesting permission: " + cmd);
      try {
        awaitTermination(uiAutomation.executeShellCommand(cmd), 2, TimeUnit.SECONDS);
      } catch (TimeoutException e) {
        Log.e(TAG, "Timeout while executing cmd: " + cmd);
      }
    }
  
    /**
     * Blocks until the command finished executing.
     *
     * <p>The reason why we need to have this method is because of the way that {@link
     * UiAutomation#executeShellCommand(String)} is currently implemented in the platform. Ideally
     * executeShellCommand() would just return the exit code to us or give us a callback.
     *
     * <p>This is a better way than just using Thread.sleep(), since it would be really hard to
     * determine what a timeout would be. We would wait too long on a fast device and it might still
     * not work on slow devices.
     *
     * <p>But this solution is not a great way either to determine if a command has finished execution
     * and there might still be situations where even this solution might be flaky. To have another
     * safety net we wait an additional 1000ms in RequestPermissionCallable if the permission was not
     * granted as expected. This seems to be the best we can do for now.
     *
     * @param pfDescriptor Used to read the content returned by shell command
     */
    private static void awaitTermination(
        ParcelFileDescriptor pfDescriptor, long timeout, TimeUnit unit)
        throws IOException, TimeoutException {
      long timeoutInMillis = unit.toMillis(timeout);
      long endTimeInMillis = timeoutInMillis > 0 ? System.currentTimeMillis() + timeoutInMillis : 0;
      BufferedReader reader = null;
      try {
        reader =
            new BufferedReader(
                new InputStreamReader(new ParcelFileDescriptor.AutoCloseInputStream(pfDescriptor)));
        String line;
        while ((line = reader.readLine()) != null) {
          Log.i(TAG, line);
          if (endTimeInMillis > System.currentTimeMillis()) {
            throw new TimeoutException();
          }
        }
      } finally {
        if (reader != null) {
          reader.close();
        }
      }
    }
  }
  