1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.autofill;
18
19import static com.android.server.autofill.AutofillManagerService.RECEIVER_BUNDLE_EXTRA_SESSIONS;
20
21import android.os.Bundle;
22import android.os.ShellCommand;
23import android.os.UserHandle;
24import android.view.autofill.AutofillManager;
25
26import com.android.internal.os.IResultReceiver;
27
28import java.io.PrintWriter;
29import java.util.ArrayList;
30import java.util.concurrent.CountDownLatch;
31import java.util.concurrent.TimeUnit;
32
33public final class AutofillManagerServiceShellCommand extends ShellCommand {
34
35    private final AutofillManagerService mService;
36
37    public AutofillManagerServiceShellCommand(AutofillManagerService service) {
38        mService = service;
39    }
40
41    @Override
42    public int onCommand(String cmd) {
43        if (cmd == null) {
44            return handleDefaultCommands(cmd);
45        }
46        final PrintWriter pw = getOutPrintWriter();
47        switch (cmd) {
48            case "list":
49                return requestList(pw);
50            case "destroy":
51                return requestDestroy(pw);
52            case "reset":
53                return requestReset();
54            case "get":
55                return requestGet(pw);
56            case "set":
57                return requestSet(pw);
58            default:
59                return handleDefaultCommands(cmd);
60        }
61    }
62
63    @Override
64    public void onHelp() {
65        try (final PrintWriter pw = getOutPrintWriter();) {
66            pw.println("AutoFill Service (autofill) commands:");
67            pw.println("  help");
68            pw.println("    Prints this help text.");
69            pw.println("");
70            pw.println("  get log_level ");
71            pw.println("    Gets the Autofill log level (off | debug | verbose).");
72            pw.println("");
73            pw.println("  get max_partitions");
74            pw.println("    Gets the maximum number of partitions per session.");
75            pw.println("");
76            pw.println("  set log_level [off | debug | verbose]");
77            pw.println("    Sets the Autofill log level.");
78            pw.println("");
79            pw.println("  set max_partitions number");
80            pw.println("    Sets the maximum number of partitions per session.");
81            pw.println("");
82            pw.println("  list sessions [--user USER_ID]");
83            pw.println("    List all pending sessions.");
84            pw.println("");
85            pw.println("  destroy sessions [--user USER_ID]");
86            pw.println("    Destroy all pending sessions.");
87            pw.println("");
88            pw.println("  reset");
89            pw.println("    Reset all pending sessions and cached service connections.");
90            pw.println("");
91        }
92    }
93
94    private int requestGet(PrintWriter pw) {
95        final String what = getNextArgRequired();
96        switch(what) {
97            case "log_level":
98                return getLogLevel(pw);
99            case "max_partitions":
100                return getMaxPartitions(pw);
101            default:
102                pw.println("Invalid set: " + what);
103                return -1;
104        }
105    }
106
107    private int requestSet(PrintWriter pw) {
108        final String what = getNextArgRequired();
109
110        switch(what) {
111            case "log_level":
112                return setLogLevel(pw);
113            case "max_partitions":
114                return setMaxPartitions();
115            default:
116                pw.println("Invalid set: " + what);
117                return -1;
118        }
119    }
120
121    private int getLogLevel(PrintWriter pw) {
122        final int logLevel = mService.getLogLevel();
123        switch (logLevel) {
124            case AutofillManager.FLAG_ADD_CLIENT_VERBOSE:
125                pw.println("verbose");
126                return 0;
127            case AutofillManager.FLAG_ADD_CLIENT_DEBUG:
128                pw.println("debug");
129                return 0;
130            case 0:
131                pw.println("off");
132                return 0;
133            default:
134                pw.println("unknow (" + logLevel + ")");
135                return 0;
136        }
137    }
138
139    private int setLogLevel(PrintWriter pw) {
140        final String logLevel = getNextArgRequired();
141        switch (logLevel.toLowerCase()) {
142            case "verbose":
143                mService.setLogLevel(AutofillManager.FLAG_ADD_CLIENT_VERBOSE);
144                return 0;
145            case "debug":
146                mService.setLogLevel(AutofillManager.FLAG_ADD_CLIENT_DEBUG);
147                return 0;
148            case "off":
149                mService.setLogLevel(0);
150                return 0;
151            default:
152                pw.println("Invalid level: " + logLevel);
153                return -1;
154        }
155    }
156
157    private int getMaxPartitions(PrintWriter pw) {
158        pw.println(mService.getMaxPartitions());
159        return 0;
160    }
161
162    private int setMaxPartitions() {
163        mService.setMaxPartitions(Integer.parseInt(getNextArgRequired()));
164        return 0;
165    }
166
167    private int requestDestroy(PrintWriter pw) {
168        if (!isNextArgSessions(pw)) {
169            return -1;
170        }
171
172        final int userId = getUserIdFromArgsOrAllUsers();
173        final CountDownLatch latch = new CountDownLatch(1);
174        final IResultReceiver receiver = new IResultReceiver.Stub() {
175            @Override
176            public void send(int resultCode, Bundle resultData) {
177                latch.countDown();
178            }
179        };
180        return requestSessionCommon(pw, latch, () -> mService.destroySessions(userId, receiver));
181    }
182
183    private int requestList(PrintWriter pw) {
184        if (!isNextArgSessions(pw)) {
185            return -1;
186        }
187
188        final int userId = getUserIdFromArgsOrAllUsers();
189        final CountDownLatch latch = new CountDownLatch(1);
190        final IResultReceiver receiver = new IResultReceiver.Stub() {
191            @Override
192            public void send(int resultCode, Bundle resultData) {
193                final ArrayList<String> sessions = resultData
194                        .getStringArrayList(RECEIVER_BUNDLE_EXTRA_SESSIONS);
195                for (String session : sessions) {
196                    pw.println(session);
197                }
198                latch.countDown();
199            }
200        };
201        return requestSessionCommon(pw, latch, () -> mService.listSessions(userId, receiver));
202    }
203
204    private boolean isNextArgSessions(PrintWriter pw) {
205        final String type = getNextArgRequired();
206        if (!type.equals("sessions")) {
207            pw.println("Error: invalid list type");
208            return false;
209        }
210        return true;
211    }
212
213    private boolean isNextArgLogLevel(PrintWriter pw, String cmd) {
214        final String type = getNextArgRequired();
215        if (!type.equals("log_level")) {
216            pw.println("Error: invalid " + cmd + " type: " + type);
217            return false;
218        }
219        return true;
220    }
221
222    private int requestSessionCommon(PrintWriter pw, CountDownLatch latch,
223            Runnable command) {
224        command.run();
225
226        try {
227            final boolean received = latch.await(5, TimeUnit.SECONDS);
228            if (!received) {
229                pw.println("Timed out after 5 seconds");
230                return -1;
231            }
232        } catch (InterruptedException e) {
233            pw.println("System call interrupted");
234            Thread.currentThread().interrupt();
235            return -1;
236        }
237        return 0;
238    }
239
240    private int requestReset() {
241        mService.reset();
242        return 0;
243    }
244
245    private int getUserIdFromArgsOrAllUsers() {
246        if ("--user".equals(getNextArg())) {
247            return UserHandle.parseUserArg(getNextArgRequired());
248        }
249        return UserHandle.USER_ALL;
250    }
251}
252