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 */
16package com.android.documentsui.queries;
17
18import static com.android.documentsui.base.Shared.DEBUG;
19
20import android.content.Context;
21import android.support.annotation.VisibleForTesting;
22import android.text.TextUtils;
23import android.util.Log;
24
25import com.android.documentsui.DocumentsApplication;
26import com.android.documentsui.R;
27import com.android.documentsui.base.DebugFlags;
28import com.android.documentsui.base.EventHandler;
29import com.android.documentsui.base.Features;
30
31import java.util.ArrayList;
32import java.util.List;
33
34public final class CommandInterceptor implements EventHandler<String> {
35
36    @VisibleForTesting
37    static final String COMMAND_PREFIX = ":";
38
39    private static final String TAG = "CommandInterceptor";
40
41    private final List<EventHandler<String[]>> mCommands = new ArrayList<>();
42
43    private Features mFeatures;
44
45    public CommandInterceptor(Features features) {
46        mFeatures = features;
47
48        mCommands.add(this::quickViewer);
49        mCommands.add(this::gestureScale);
50        mCommands.add(this::archiveCreation);
51        mCommands.add(this::docDetails);
52        mCommands.add(this::forcePaging);
53    }
54
55    public void add(EventHandler<String[]> handler) {
56        mCommands.add(handler);
57    }
58
59    @Override
60    public boolean accept(String query) {
61        if (!mFeatures.isCommandInterceptorEnabled()) {
62            if (DEBUG) Log.v(TAG, "Skipping input, command interceptor disabled.");
63            return false;
64        }
65
66        if (query.length() > COMMAND_PREFIX.length() && query.startsWith(COMMAND_PREFIX)) {
67            String[] tokens = query.substring(COMMAND_PREFIX.length()).split("\\s+");
68            for (EventHandler<String[]> command : mCommands) {
69                if (command.accept(tokens)) {
70                    return true;
71                }
72            }
73            Log.d(TAG, "Unrecognized debug command: " + query);
74        }
75        return false;
76    }
77
78    private boolean quickViewer(String[] tokens) {
79        if ("qv".equals(tokens[0])) {
80            if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) {
81                DebugFlags.setQuickViewer(tokens[1]);
82                Log.i(TAG, "Set quick viewer to: " + tokens[1]);
83                return true;
84            } else {
85                Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens));
86            }
87        } else if ("deqv".equals(tokens[0])) {
88            Log.i(TAG, "Unset quick viewer");
89            DebugFlags.setQuickViewer(null);
90            return true;
91        }
92        return false;
93    }
94
95    private boolean gestureScale(String[] tokens) {
96        if ("gs".equals(tokens[0])) {
97            if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) {
98                boolean enabled = asBool(tokens[1]);
99                mFeatures.forceFeature(R.bool.feature_gesture_scale, enabled);
100                Log.i(TAG, "Set gesture scale enabled to: " + enabled);
101                return true;
102            }
103            Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens));
104        }
105        return false;
106    }
107
108    private boolean archiveCreation(String[] tokens) {
109        if ("zip".equals(tokens[0])) {
110            if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) {
111                boolean enabled = asBool(tokens[1]);
112                mFeatures.forceFeature(R.bool.feature_archive_creation, enabled);
113                Log.i(TAG, "Set gesture scale enabled to: " + enabled);
114                return true;
115            }
116            Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens));
117        }
118        return false;
119    }
120
121    private boolean docDetails(String[] tokens) {
122        if ("docinfo".equals(tokens[0])) {
123            if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) {
124                boolean enabled = asBool(tokens[1]);
125                DebugFlags.setDocumentDetailsEnabled(enabled);
126                Log.i(TAG, "Set gesture scale enabled to: " + enabled);
127                return true;
128            }
129            Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens));
130        }
131        return false;
132    }
133
134    private boolean forcePaging(String[] tokens) {
135        if ("page".equals(tokens[0])) {
136            if (tokens.length >= 2) {
137                try {
138                    int offset = Integer.parseInt(tokens[1]);
139                    int limit = (tokens.length == 3) ? Integer.parseInt(tokens[2]) : -1;
140                    DebugFlags.setForcedPaging(offset, limit);
141                    Log.i(TAG, "Set forced paging to offset: " + offset + ", limit: " + limit);
142                    return true;
143                } catch (NumberFormatException e) {
144                    Log.w(TAG, "Command input does not contain valid numbers: "
145                            + TextUtils.join(" ", tokens));
146                    return false;
147                }
148            } else {
149                Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens));
150            }
151        } else if ("deqv".equals(tokens[0])) {
152            Log.i(TAG, "Unset quick viewer");
153            DebugFlags.setQuickViewer(null);
154            return true;
155        }
156        return false;
157    }
158
159    private final boolean asBool(String val) {
160        if (val == null || val.equals("0")) {
161            return false;
162        }
163        if (val.equals("1")) {
164            return true;
165        }
166        return Boolean.valueOf(val);
167    }
168
169    public static final class DumpRootsCacheHandler implements EventHandler<String[]> {
170        private final Context mContext;
171
172        public DumpRootsCacheHandler(Context context) {
173            mContext = context;
174        }
175
176        @Override
177        public boolean accept(String[] tokens) {
178            if ("dumpCache".equals(tokens[0])) {
179                DocumentsApplication.getProvidersCache(mContext).logCache();
180                return true;
181            }
182            return false;
183        }
184    }
185}
186