| /* |
| * 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 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.documentsui.queries; |
| |
| import static com.android.documentsui.base.SharedMinimal.DEBUG; |
| |
| import android.content.Context; |
| import androidx.annotation.VisibleForTesting; |
| import android.text.TextUtils; |
| import android.util.Log; |
| |
| import com.android.documentsui.DocumentsApplication; |
| import com.android.documentsui.R; |
| import com.android.documentsui.base.DebugFlags; |
| import com.android.documentsui.base.EventHandler; |
| import com.android.documentsui.base.Features; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| public final class CommandInterceptor implements EventHandler<String> { |
| |
| @VisibleForTesting |
| static final String COMMAND_PREFIX = ":"; |
| |
| private static final String TAG = "CommandInterceptor"; |
| |
| private final List<EventHandler<String[]>> mCommands = new ArrayList<>(); |
| |
| private Features mFeatures; |
| |
| public CommandInterceptor(Features features) { |
| mFeatures = features; |
| |
| mCommands.add(this::quickViewer); |
| mCommands.add(this::gestureScale); |
| mCommands.add(this::jobProgressDialog); |
| mCommands.add(this::docDetails); |
| mCommands.add(this::forcePaging); |
| } |
| |
| public void add(EventHandler<String[]> handler) { |
| mCommands.add(handler); |
| } |
| |
| @Override |
| public boolean accept(String query) { |
| if (!mFeatures.isDebugSupportEnabled()) { |
| return false; |
| } |
| |
| if (!mFeatures.isCommandInterceptorEnabled()) { |
| if (DEBUG) { |
| Log.v(TAG, "Skipping input, command interceptor disabled."); |
| } |
| return false; |
| } |
| |
| if (query.length() > COMMAND_PREFIX.length() && query.startsWith(COMMAND_PREFIX)) { |
| String[] tokens = query.substring(COMMAND_PREFIX.length()).split("\\s+"); |
| for (EventHandler<String[]> command : mCommands) { |
| if (command.accept(tokens)) { |
| return true; |
| } |
| } |
| Log.d(TAG, "Unrecognized debug command: " + query); |
| } |
| return false; |
| } |
| |
| private boolean quickViewer(String[] tokens) { |
| if ("qv".equals(tokens[0])) { |
| if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) { |
| DebugFlags.setQuickViewer(tokens[1]); |
| Log.i(TAG, "Set quick viewer to: " + tokens[1]); |
| return true; |
| } else { |
| Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens)); |
| } |
| } else if ("deqv".equals(tokens[0])) { |
| Log.i(TAG, "Unset quick viewer"); |
| DebugFlags.setQuickViewer(null); |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean gestureScale(String[] tokens) { |
| if ("gs".equals(tokens[0])) { |
| if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) { |
| boolean enabled = asBool(tokens[1]); |
| mFeatures.forceFeature(R.bool.feature_gesture_scale, enabled); |
| Log.i(TAG, "Set gesture scale enabled to: " + enabled); |
| return true; |
| } |
| Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens)); |
| } |
| return false; |
| } |
| |
| private boolean jobProgressDialog(String[] tokens) { |
| if ("jpd".equals(tokens[0])) { |
| if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) { |
| boolean enabled = asBool(tokens[1]); |
| mFeatures.forceFeature(R.bool.feature_job_progress_dialog, enabled); |
| Log.i(TAG, "Set job progress dialog enabled to: " + enabled); |
| return true; |
| } |
| Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens)); |
| } |
| return false; |
| } |
| |
| // Include document debug info in Get Info panel. |
| // enabled by default on DEBUG builds. |
| private boolean docDetails(String[] tokens) { |
| if ("docinfo".equals(tokens[0])) { |
| if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) { |
| boolean enabled = asBool(tokens[1]); |
| DebugFlags.setDocumentDetailsEnabled(enabled); |
| Log.i(TAG, "Set doc details enabled to: " + enabled); |
| return true; |
| } |
| Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens)); |
| } |
| return false; |
| } |
| |
| private boolean forcePaging(String[] tokens) { |
| if ("page".equals(tokens[0])) { |
| if (tokens.length >= 2) { |
| try { |
| int offset = Integer.parseInt(tokens[1]); |
| int limit = (tokens.length == 3) ? Integer.parseInt(tokens[2]) : -1; |
| DebugFlags.setForcedPaging(offset, limit); |
| Log.i(TAG, "Set forced paging to offset: " + offset + ", limit: " + limit); |
| return true; |
| } catch (NumberFormatException e) { |
| Log.w(TAG, "Command input does not contain valid numbers: " |
| + TextUtils.join(" ", tokens)); |
| return false; |
| } |
| } else { |
| Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens)); |
| } |
| } else if ("deqv".equals(tokens[0])) { |
| Log.i(TAG, "Unset quick viewer"); |
| DebugFlags.setQuickViewer(null); |
| return true; |
| } |
| return false; |
| } |
| |
| private final boolean asBool(String val) { |
| if (val == null || val.equals("0")) { |
| return false; |
| } |
| if (val.equals("1")) { |
| return true; |
| } |
| return Boolean.valueOf(val); |
| } |
| |
| public static final class DumpRootsCacheHandler implements EventHandler<String[]> { |
| private final Context mContext; |
| |
| public DumpRootsCacheHandler(Context context) { |
| mContext = context; |
| } |
| |
| @Override |
| public boolean accept(String[] tokens) { |
| if ("dumpCache".equals(tokens[0])) { |
| DocumentsApplication.getProvidersCache(mContext).logCache(); |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * Wraps {@link CommandInterceptor} in a tiny decorator that adds support for |
| * enabling CommandInterceptor feature based on some magic query input. |
| * |
| * <p>It's like super meta, maaaannn. |
| */ |
| public static final EventHandler<String> createDebugModeFlipper( |
| Features features, |
| Runnable debugFlipper, |
| CommandInterceptor interceptor) { |
| |
| if (!features.isDebugSupportEnabled()) { |
| return interceptor; |
| } |
| |
| String magicString1 = COMMAND_PREFIX + "wwssadadba"; |
| String magicString2 = "up up down down left right left right b a"; |
| |
| return new EventHandler<String>() { |
| @Override |
| public boolean accept(String query) { |
| assert(features.isDebugSupportEnabled()); |
| |
| if (magicString1.equals(query) || magicString2.equals(query)) { |
| debugFlipper.run(); |
| } |
| return interceptor.accept(query); |
| } |
| }; |
| } |
| } |