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

Commit 354736e1 authored by Dianne Hackborn's avatar Dianne Hackborn
Browse files

New infrastructure to switch remaining commands to "cmd" calls.

This introduces a new feature of the IBinder command protocol
to allow the shell command implementation to call back into
its caller to ask it to open files in the calling context.  This
is needed so that commands that have arguments specifying files
can open those files as the calling shell, not the system (or
whatever) process.

To test this all out, move the "am start" implementation over
to ActivityManagerShellCommand, in particular along with its
option to specify a file in which to write profiling data.

Test: Manual

Change-Id: I0c1e3857defefbd19a2ac29413aafbb34b1e48a3
parent 015deed8
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -332,6 +332,7 @@ LOCAL_SRC_FILES += \
	core/java/com/android/internal/os/IDropBoxManagerService.aidl \
	core/java/com/android/internal/os/IParcelFileDescriptorFactory.aidl \
	core/java/com/android/internal/os/IResultReceiver.aidl \
	core/java/com/android/internal/os/IShellCallback.aidl \
	core/java/com/android/internal/statusbar/IStatusBar.aidl \
	core/java/com/android/internal/statusbar/IStatusBarService.aidl \
	core/java/com/android/internal/textservice/ISpellCheckerService.aidl \
+51 −216
Original line number Diff line number Diff line
@@ -26,7 +26,6 @@ import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import android.app.ActivityManager;
import android.app.ActivityManager.StackInfo;
import android.app.ActivityManagerNative;
import android.app.ActivityOptions;
import android.app.IActivityContainer;
import android.app.IActivityController;
import android.app.IActivityManager;
@@ -46,7 +45,6 @@ import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.InstrumentationInfo;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -55,10 +53,11 @@ import android.os.Build;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SELinux;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -72,6 +71,7 @@ import com.android.internal.util.Preconditions;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -96,6 +96,7 @@ public class Am extends BaseCommand {
    // Amount we reduce the stack size by when testing a task re-size.
    private static final int STACK_BOUNDS_INSET = 10;

    public static final String NO_CLASS_ERROR_CODE = "Error type 3";
    private IActivityManager mAm;
    private IPackageManager mPm;

@@ -198,7 +199,7 @@ public class Am extends BaseCommand {
                "    --track-allocation: enable tracking of object allocations\n" +
                "    --user <USER_ID> | current: Specify which user to run as; if not\n" +
                "        specified then run as the current user.\n" +
                "    --stack <STACK_ID>: Specify into which stack should the activity be put." +
                "    --stack <STACK_ID>: Specify into which stack should the activity be put.\n" +
                "\n" +
                "am startservice: start a Service.  Options are:\n" +
                "    --user <USER_ID> | current: Specify which user to run as; if not\n" +
@@ -385,17 +386,13 @@ public class Am extends BaseCommand {
        String op = nextArgRequired();

        if (op.equals("start")) {
            runStart();
            runAmCmd(getRawArgs());
        } else if (op.equals("startservice")) {
            runStartService();
        } else if (op.equals("stopservice")) {
            runStopService();
        } else if (op.equals("force-stop")) {
            runForceStop();
        } else if (op.equals("kill")) {
            runKill();
        } else if (op.equals("kill-all")) {
            runKillAll();
        } else if (op.equals("force-stop") || op.equals("kill") || op.equals("kill-all")) {
            runAmCmd(getRawArgs());
        } else if (op.equals("instrument")) {
            runInstrument();
        } else if (op.equals("trace-ipc")) {
@@ -475,6 +472,49 @@ public class Am extends BaseCommand {
        return userId;
    }

    static final class MyShellCallback extends ShellCallback {
        @Override public ParcelFileDescriptor onOpenOutputFile(String path, String seLinuxContext) {
            File file = new File(path);
            //System.err.println("Opening file: " + file.getAbsolutePath());
            //Log.i("Am", "Opening file: " + file.getAbsolutePath());
            final ParcelFileDescriptor fd;
            try {
                fd = ParcelFileDescriptor.open(file,
                        ParcelFileDescriptor.MODE_CREATE |
                        ParcelFileDescriptor.MODE_TRUNCATE |
                        ParcelFileDescriptor.MODE_WRITE_ONLY);
            } catch (FileNotFoundException e) {
                String msg = "Unable to open file " + path + ": " + e;
                System.err.println(msg);
                throw new IllegalArgumentException(msg);
            }
            if (seLinuxContext != null) {
                final String tcon = SELinux.getFileContext(file.getAbsolutePath());
                if (!SELinux.checkSELinuxAccess(seLinuxContext, tcon, "file", "write")) {
                    try {
                        fd.close();
                    } catch (IOException e) {
                    }
                    String msg = "System server has no access to file context " + tcon;
                    System.err.println(msg + " (from path " + file.getAbsolutePath()
                            + ", context " + seLinuxContext + ")");
                    throw new IllegalArgumentException(msg);
                }
            }
            return fd;
        }
    }

    void runAmCmd(String[] args) throws AndroidException {
        try {
            mAm.asBinder().shellCommand(FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
                    args, new MyShellCallback(), new ResultReceiver(null) { });
        } catch (RemoteException e) {
            System.err.println(NO_SYSTEM_ERROR_CODE);
            throw new AndroidException("Can't call activity manager; is the system running?");
        }
    }

    private Intent makeIntent(int defUser) throws URISyntaxException {
        mStartFlags = 0;
        mWaitOption = false;
@@ -558,211 +598,6 @@ public class Am extends BaseCommand {
        }
    }

    private void runStart() throws Exception {
        Intent intent = makeIntent(UserHandle.USER_CURRENT);

        if (mUserId == UserHandle.USER_ALL) {
            System.err.println("Error: Can't start service with user 'all'");
            return;
        }

        String mimeType = intent.getType();
        if (mimeType == null && intent.getData() != null
                && "content".equals(intent.getData().getScheme())) {
            mimeType = mAm.getProviderMimeType(intent.getData(), mUserId);
        }


        do {
            if (mStopOption) {
                String packageName;
                if (intent.getComponent() != null) {
                    packageName = intent.getComponent().getPackageName();
                } else {
                    List<ResolveInfo> activities = mPm.queryIntentActivities(intent, mimeType, 0,
                            mUserId).getList();
                    if (activities == null || activities.size() <= 0) {
                        System.err.println("Error: Intent does not match any activities: "
                                + intent);
                        return;
                    } else if (activities.size() > 1) {
                        System.err.println("Error: Intent matches multiple activities; can't stop: "
                                + intent);
                        return;
                    }
                    packageName = activities.get(0).activityInfo.packageName;
                }
                System.out.println("Stopping: " + packageName);
                mAm.forceStopPackage(packageName, mUserId);
                Thread.sleep(250);
            }

            System.out.println("Starting: " + intent);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

            ParcelFileDescriptor fd = null;
            ProfilerInfo profilerInfo = null;

            if (mProfileFile != null) {
                try {
                    fd = openForSystemServer(
                            new File(mProfileFile),
                            ParcelFileDescriptor.MODE_CREATE |
                            ParcelFileDescriptor.MODE_TRUNCATE |
                            ParcelFileDescriptor.MODE_WRITE_ONLY);
                } catch (FileNotFoundException e) {
                    System.err.println("Error: Unable to open file: " + mProfileFile);
                    System.err.println("Consider using a file under /data/local/tmp/");
                    return;
                }
                profilerInfo = new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop);
            }

            IActivityManager.WaitResult result = null;
            int res;
            final long startTime = SystemClock.uptimeMillis();
            ActivityOptions options = null;
            if (mStackId != INVALID_STACK_ID) {
                options = ActivityOptions.makeBasic();
                options.setLaunchStackId(mStackId);
            }
            if (mWaitOption) {
                result = mAm.startActivityAndWait(null, null, intent, mimeType,
                        null, null, 0, mStartFlags, profilerInfo,
                        options != null ? options.toBundle() : null, mUserId);
                res = result.result;
            } else {
                res = mAm.startActivityAsUser(null, null, intent, mimeType,
                        null, null, 0, mStartFlags, profilerInfo,
                        options != null ? options.toBundle() : null, mUserId);
            }
            final long endTime = SystemClock.uptimeMillis();
            PrintStream out = mWaitOption ? System.out : System.err;
            boolean launched = false;
            switch (res) {
                case ActivityManager.START_SUCCESS:
                    launched = true;
                    break;
                case ActivityManager.START_SWITCHES_CANCELED:
                    launched = true;
                    out.println(
                            "Warning: Activity not started because the "
                            + " current activity is being kept for the user.");
                    break;
                case ActivityManager.START_DELIVERED_TO_TOP:
                    launched = true;
                    out.println(
                            "Warning: Activity not started, intent has "
                            + "been delivered to currently running "
                            + "top-most instance.");
                    break;
                case ActivityManager.START_RETURN_INTENT_TO_CALLER:
                    launched = true;
                    out.println(
                            "Warning: Activity not started because intent "
                            + "should be handled by the caller");
                    break;
                case ActivityManager.START_TASK_TO_FRONT:
                    launched = true;
                    out.println(
                            "Warning: Activity not started, its current "
                            + "task has been brought to the front");
                    break;
                case ActivityManager.START_INTENT_NOT_RESOLVED:
                    out.println(
                            "Error: Activity not started, unable to "
                            + "resolve " + intent.toString());
                    break;
                case ActivityManager.START_CLASS_NOT_FOUND:
                    out.println(NO_CLASS_ERROR_CODE);
                    out.println("Error: Activity class " +
                            intent.getComponent().toShortString()
                            + " does not exist.");
                    break;
                case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
                    out.println(
                            "Error: Activity not started, you requested to "
                            + "both forward and receive its result");
                    break;
                case ActivityManager.START_PERMISSION_DENIED:
                    out.println(
                            "Error: Activity not started, you do not "
                            + "have permission to access it.");
                    break;
                case ActivityManager.START_NOT_VOICE_COMPATIBLE:
                    out.println(
                            "Error: Activity not started, voice control not allowed for: "
                                    + intent);
                    break;
                case ActivityManager.START_NOT_CURRENT_USER_ACTIVITY:
                    out.println(
                            "Error: Not allowed to start background user activity"
                            + " that shouldn't be displayed for all users.");
                    break;
                default:
                    out.println(
                            "Error: Activity not started, unknown error code " + res);
                    break;
            }
            if (mWaitOption && launched) {
                if (result == null) {
                    result = new IActivityManager.WaitResult();
                    result.who = intent.getComponent();
                }
                System.out.println("Status: " + (result.timeout ? "timeout" : "ok"));
                if (result.who != null) {
                    System.out.println("Activity: " + result.who.flattenToShortString());
                }
                if (result.thisTime >= 0) {
                    System.out.println("ThisTime: " + result.thisTime);
                }
                if (result.totalTime >= 0) {
                    System.out.println("TotalTime: " + result.totalTime);
                }
                System.out.println("WaitTime: " + (endTime-startTime));
                System.out.println("Complete");
            }
            mRepeat--;
            if (mRepeat > 0) {
                mAm.unhandledBack();
            }
        } while (mRepeat > 0);
    }

    private void runForceStop() throws Exception {
        int userId = UserHandle.USER_ALL;

        String opt;
        while ((opt=nextOption()) != null) {
            if (opt.equals("--user")) {
                userId = parseUserArg(nextArgRequired());
            } else {
                System.err.println("Error: Unknown option: " + opt);
                return;
            }
        }
        mAm.forceStopPackage(nextArgRequired(), userId);
    }

    private void runKill() throws Exception {
        int userId = UserHandle.USER_ALL;

        String opt;
        while ((opt=nextOption()) != null) {
            if (opt.equals("--user")) {
                userId = parseUserArg(nextArgRequired());
            } else {
                System.err.println("Error: Unknown option: " + opt);
                return;
            }
        }
        mAm.killBackgroundProcesses(nextArgRequired(), userId);
    }

    private void runKillAll() throws Exception {
        mAm.killAllBackgroundProcesses();
    }

    private void sendBroadcast() throws Exception {
        Intent intent = makeIntent(UserHandle.USER_CURRENT);
        IntentReceiver receiver = new IntentReceiver();
+37 −1
Original line number Diff line number Diff line
@@ -49,9 +49,12 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IUserManager;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SELinux;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
@@ -68,6 +71,7 @@ import libcore.io.IoUtils;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -284,13 +288,45 @@ public final class Pm {
        }
    }

    static final class MyShellCallback extends ShellCallback {
        @Override public ParcelFileDescriptor onOpenOutputFile(String path, String seLinuxContext) {
            File file = new File(path);
            final ParcelFileDescriptor fd;
            try {
                fd = ParcelFileDescriptor.open(file,
                            ParcelFileDescriptor.MODE_CREATE |
                            ParcelFileDescriptor.MODE_TRUNCATE |
                            ParcelFileDescriptor.MODE_WRITE_ONLY);
            } catch (FileNotFoundException e) {
                String msg = "Unable to open file " + path + ": " + e;
                System.err.println(msg);
                throw new IllegalArgumentException(msg);
            }
            if (seLinuxContext != null) {
                final String tcon = SELinux.getFileContext(file.getAbsolutePath());
                if (!SELinux.checkSELinuxAccess(seLinuxContext, tcon, "file", "write")) {
                    try {
                        fd.close();
                    } catch (IOException e) {
                    }
                    String msg = "System server has no access to file context " + tcon;
                    System.err.println(msg + " (from path " + file.getAbsolutePath()
                            + ", context " + seLinuxContext + ")");
                    throw new IllegalArgumentException(msg);
                }
            }
            return fd;
        }
    }

    private int runShellCommand(String serviceName, String[] args) {
        final HandlerThread handlerThread = new HandlerThread("results");
        handlerThread.start();
        try {
            ServiceManager.getService(serviceName).shellCommand(
                    FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
                    args, new ResultReceiver(new Handler(handlerThread.getLooper())));
                    args, new MyShellCallback(),
                    new ResultReceiver(new Handler(handlerThread.getLooper())));
            return 0;
        } catch (RemoteException e) {
            e.printStackTrace();
+10 −6
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ import libcore.io.IoUtils;

import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.lang.reflect.Modifier;
@@ -361,13 +360,14 @@ public class Binder implements IBinder {
            ParcelFileDescriptor out = data.readFileDescriptor();
            ParcelFileDescriptor err = data.readFileDescriptor();
            String[] args = data.readStringArray();
            ShellCallback shellCallback = ShellCallback.CREATOR.createFromParcel(data);
            ResultReceiver resultReceiver = ResultReceiver.CREATOR.createFromParcel(data);
            try {
                if (out != null) {
                    shellCommand(in != null ? in.getFileDescriptor() : null,
                            out.getFileDescriptor(),
                            err != null ? err.getFileDescriptor() : out.getFileDescriptor(),
                            args, resultReceiver);
                            args, shellCallback, resultReceiver);
                }
            } finally {
                IoUtils.closeQuietly(in);
@@ -459,13 +459,15 @@ public class Binder implements IBinder {
     * @param out The raw file descriptor that normal command messages should be written to.
     * @param err The raw file descriptor that command error messages should be written to.
     * @param args Command-line arguments.
     * @param callback Callback through which to interact with the invoking shell.
     * @param resultReceiver Called when the command has finished executing, with the result code.
     * @throws RemoteException
     * @hide
     */
    public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
            String[] args, ResultReceiver resultReceiver) throws RemoteException {
        onShellCommand(in, out, err, args, resultReceiver);
            String[] args, ShellCallback callback,
            ResultReceiver resultReceiver) throws RemoteException {
        onShellCommand(in, out, err, args, callback, resultReceiver);
    }

    /**
@@ -477,7 +479,7 @@ public class Binder implements IBinder {
     * @hide
     */
    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
            String[] args, ResultReceiver resultReceiver) throws RemoteException {
            String[] args, ShellCallback callback, ResultReceiver resultReceiver) throws RemoteException {
        FileOutputStream fout = new FileOutputStream(err != null ? err : out);
        PrintWriter pw = new FastPrintWriter(fout);
        pw.println("No shell command implementation.");
@@ -650,13 +652,15 @@ final class BinderProxy implements IBinder {
    }

    public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
            String[] args, ResultReceiver resultReceiver) throws RemoteException {
            String[] args, ShellCallback callback,
            ResultReceiver resultReceiver) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeFileDescriptor(in);
        data.writeFileDescriptor(out);
        data.writeFileDescriptor(err);
        data.writeStringArray(args);
        ShellCallback.writeToParcel(callback, data);
        resultReceiver.writeToParcel(data, 0);
        try {
            transact(SHELL_COMMAND_TRANSACTION, data, reply, 0);
+3 −1
Original line number Diff line number Diff line
@@ -220,11 +220,13 @@ public interface IBinder {
     * @param out The raw file descriptor that normal command messages should be written to.
     * @param err The raw file descriptor that command error messages should be written to.
     * @param args Command-line arguments.
     * @param shellCallback Optional callback to the caller's shell to perform operations in it.
     * @param resultReceiver Called when the command has finished executing, with the result code.
     * @hide
     */
    public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
            String[] args, ResultReceiver resultReceiver) throws RemoteException;
            String[] args, ShellCallback shellCallback,
            ResultReceiver resultReceiver) throws RemoteException;

    /**
     * Perform a generic operation with the object.
Loading