From 2ccd4a1f40d371100b94c676c4c39c97829e153b Mon Sep 17 00:00:00 2001 From: twyen Date: Mon, 22 Jan 2018 11:52:16 -0800 Subject: Implement blocking commands Test: Unit tests PiperOrigin-RevId: 182813080 Change-Id: I952f49352fb57c02c4efb9cc4ede84dc7c32c893 --- .../dialer/blocking/FilteredNumberCompat.java | 5 +- java/com/android/dialer/commandline/Arguments.java | 144 +++++++++++++++++++++ java/com/android/dialer/commandline/Command.java | 20 ++- .../dialer/commandline/CommandLineModule.java | 6 +- .../dialer/commandline/CommandLineReceiver.java | 28 ++-- .../android/dialer/commandline/impl/Blocking.java | 96 ++++++++++++++ java/com/android/dialer/commandline/impl/Echo.java | 24 ++-- java/com/android/dialer/commandline/impl/Help.java | 36 +++--- .../android/dialer/commandline/impl/Version.java | 22 ++-- .../DialerBlockedNumberPhoneLookup.java | 3 +- 10 files changed, 333 insertions(+), 51 deletions(-) create mode 100644 java/com/android/dialer/commandline/Arguments.java create mode 100644 java/com/android/dialer/commandline/impl/Blocking.java diff --git a/java/com/android/dialer/blocking/FilteredNumberCompat.java b/java/com/android/dialer/blocking/FilteredNumberCompat.java index a5f3d7efd..bea84e8db 100644 --- a/java/com/android/dialer/blocking/FilteredNumberCompat.java +++ b/java/com/android/dialer/blocking/FilteredNumberCompat.java @@ -34,6 +34,7 @@ import android.support.annotation.VisibleForTesting; import android.telecom.TelecomManager; import android.telephony.PhoneNumberUtils; import com.android.dialer.common.LogUtil; +import com.android.dialer.configprovider.ConfigProviderBindings; import com.android.dialer.database.FilteredNumberContract.FilteredNumber; import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns; import com.android.dialer.database.FilteredNumberContract.FilteredNumberSources; @@ -117,7 +118,9 @@ public class FilteredNumberCompat { * migration has been performed, {@code false} otherwise. */ public static boolean useNewFiltering(Context context) { - return canUseNewFiltering() && hasMigratedToNewBlocking(context); + return !ConfigProviderBindings.get(context).getBoolean("debug_force_dialer_filtering", false) + && canUseNewFiltering() + && hasMigratedToNewBlocking(context); } /** diff --git a/java/com/android/dialer/commandline/Arguments.java b/java/com/android/dialer/commandline/Arguments.java new file mode 100644 index 000000000..84ed0e422 --- /dev/null +++ b/java/com/android/dialer/commandline/Arguments.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2018 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.dialer.commandline; + +import android.support.annotation.Nullable; +import com.android.dialer.commandline.Command.IllegalCommandLineArgumentException; +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterators; +import com.google.common.collect.PeekingIterator; +import com.google.common.collect.UnmodifiableIterator; + +/** + * Parses command line arguments into optional flags (--foo, --key=value, --key value) and required + * positionals (which must be passed in order). Flags must start with "--" and are always before + * positionals. If flags are used "--" must be placed before positionals. + * + *

--flag will be interpreted as --flag=true, and --noflag as --flag=false + * + *

Grammar:
+ * dialer-cmd.py
+ * = ( -- ) |
+ * = "no"?()?
+ * = " " | "=" + */ +@AutoValue +public abstract class Arguments { + + public static final Arguments EMPTY = + new AutoValue_Arguments(ImmutableMap.of(), ImmutableList.of()); + + public abstract ImmutableMap getFlags(); + + public abstract ImmutableList getPositionals(); + + /** + * Return the positional at {@code position}. Throw {@link IllegalCommandLineArgumentException} if + * it is absent and reports to the user {@code name} is expected. + */ + public String expectPositional(int position, String name) + throws IllegalCommandLineArgumentException { + if (getPositionals().size() <= position) { + throw new IllegalCommandLineArgumentException(name + " expected"); + } + return getPositionals().get(position); + } + + public Boolean getBoolean(String flag, boolean defaultValue) + throws IllegalCommandLineArgumentException { + if (!getFlags().containsKey(flag)) { + return defaultValue; + } + switch (getFlags().get(flag)) { + case "true": + return true; + case "false": + return false; + default: + throw new IllegalCommandLineArgumentException("boolean value expected for " + flag); + } + } + + public static Arguments parse(@Nullable String[] rawArguments) + throws IllegalCommandLineArgumentException { + if (rawArguments == null) { + return EMPTY; + } + return parse(Iterators.forArray(rawArguments)); + } + + public static Arguments parse(Iterable rawArguments) + throws IllegalCommandLineArgumentException { + return parse(Iterators.unmodifiableIterator(rawArguments.iterator())); + } + + public static Arguments parse(UnmodifiableIterator iterator) + throws IllegalCommandLineArgumentException { + PeekingIterator peekingIterator = Iterators.peekingIterator(iterator); + ImmutableMap flags = parseFlags(peekingIterator); + ImmutableList positionals = parsePositionals(peekingIterator); + + return new AutoValue_Arguments(flags, positionals); + } + + private static ImmutableMap parseFlags(PeekingIterator iterator) + throws IllegalCommandLineArgumentException { + ImmutableMap.Builder flags = ImmutableMap.builder(); + if (!iterator.hasNext()) { + return flags.build(); + } + if (!iterator.peek().startsWith("--")) { + return flags.build(); + } + + while (iterator.hasNext()) { + String peek = iterator.peek(); + if (peek.equals("--")) { + iterator.next(); + return flags.build(); + } + if (peek.startsWith("--")) { + String key = iterator.next().substring(2); + String value; + if (iterator.hasNext() && !iterator.peek().startsWith("--")) { + value = iterator.next(); + } else if (key.contains("=")) { + String[] entry = key.split("=", 2); + key = entry[0]; + value = entry[1]; + } else if (key.startsWith("no")) { + key = key.substring(2); + value = "false"; + } else { + value = "true"; + } + flags.put(key, value); + } else { + throw new IllegalCommandLineArgumentException("flag or '--' expected"); + } + } + return flags.build(); + } + + private static ImmutableList parsePositionals(PeekingIterator iterator) { + ImmutableList.Builder positionals = ImmutableList.builder(); + positionals.addAll(iterator); + return positionals.build(); + } +} diff --git a/java/com/android/dialer/commandline/Command.java b/java/com/android/dialer/commandline/Command.java index 5a35d4002..83618a255 100644 --- a/java/com/android/dialer/commandline/Command.java +++ b/java/com/android/dialer/commandline/Command.java @@ -17,15 +17,31 @@ package com.android.dialer.commandline; import android.support.annotation.NonNull; -import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.ListenableFuture; /** Handles a Command from {@link CommandLineReceiver}. */ public interface Command { - ListenableFuture run(ImmutableList args); + /** + * Thrown when {@code args} in {@link #run(Arguments)} does not match the expected format. The + * commandline will print {@code message} and {@link #getUsage()}. + */ + class IllegalCommandLineArgumentException extends Exception { + public IllegalCommandLineArgumentException(String message) { + super(message); + } + } /** Describe the command when "help" is listing available commands. */ @NonNull String getShortDescription(); + + /** + * Call when 'command --help' is called or when {@link IllegalCommandLineArgumentException} is + * thrown to inform the user how should the command be used. + */ + @NonNull + String getUsage(); + + ListenableFuture run(Arguments args) throws IllegalCommandLineArgumentException; } diff --git a/java/com/android/dialer/commandline/CommandLineModule.java b/java/com/android/dialer/commandline/CommandLineModule.java index 366899e5e..9022d6cd5 100644 --- a/java/com/android/dialer/commandline/CommandLineModule.java +++ b/java/com/android/dialer/commandline/CommandLineModule.java @@ -16,6 +16,7 @@ package com.android.dialer.commandline; +import com.android.dialer.commandline.impl.Blocking; import com.android.dialer.commandline.impl.Echo; import com.android.dialer.commandline.impl.Help; import com.android.dialer.commandline.impl.Version; @@ -41,18 +42,21 @@ public abstract class CommandLineModule { private final Help help; private final Version version; private final Echo echo; + private final Blocking blocking; @Inject - AospCommandInjector(Help help, Version version, Echo echo) { + AospCommandInjector(Help help, Version version, Echo echo, Blocking blocking) { this.help = help; this.version = version; this.echo = echo; + this.blocking = blocking; } public CommandSupplier.Builder inject(CommandSupplier.Builder builder) { builder.addCommand("help", help); builder.addCommand("version", version); builder.addCommand("echo", echo); + builder.addCommand("blocking", blocking); return builder; } } diff --git a/java/com/android/dialer/commandline/CommandLineReceiver.java b/java/com/android/dialer/commandline/CommandLineReceiver.java index baaadf054..e5e78c46a 100644 --- a/java/com/android/dialer/commandline/CommandLineReceiver.java +++ b/java/com/android/dialer/commandline/CommandLineReceiver.java @@ -21,8 +21,8 @@ import android.content.Context; import android.content.Intent; import android.text.TextUtils; import com.android.dialer.buildtype.BuildType; +import com.android.dialer.commandline.Command.IllegalCommandLineArgumentException; import com.android.dialer.common.LogUtil; -import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.MoreExecutors; @@ -53,17 +53,18 @@ public class CommandLineReceiver extends BroadcastReceiver { .commandSupplier() .get() .get(intent.getStringExtra(COMMAND)); - if (command == null) { - LogUtil.i(outputTag, "unknown command " + intent.getStringExtra(COMMAND)); - return; - } + try { + if (command == null) { + LogUtil.i(outputTag, "unknown command " + intent.getStringExtra(COMMAND)); + return; + } - ImmutableList args = - intent.hasExtra(ARGS) - ? ImmutableList.copyOf(intent.getStringArrayExtra(ARGS)) - : ImmutableList.of(); + Arguments args = Arguments.parse(intent.getStringArrayExtra(ARGS)); - try { + if (args.getBoolean("help", false)) { + LogUtil.i(outputTag, "usage:\n" + command.getUsage()); + return; + } Futures.addCallback( command.run(args), new FutureCallback() { @@ -78,12 +79,15 @@ public class CommandLineReceiver extends BroadcastReceiver { @Override public void onFailure(Throwable throwable) { - // LogUtil.e(tag, message, e) prints 2 entries where only the first one can be - // intercepted by the script. Compose the string instead. + if (throwable instanceof IllegalCommandLineArgumentException) { + LogUtil.e(outputTag, throwable.getMessage() + "\n\nusage:\n" + command.getUsage()); + } LogUtil.e(outputTag, "error running command future", throwable); } }, MoreExecutors.directExecutor()); + } catch (IllegalCommandLineArgumentException e) { + LogUtil.e(outputTag, e.getMessage() + "\n\nusage:\n" + command.getUsage()); } catch (Throwable throwable) { LogUtil.e(outputTag, "error running command", throwable); } diff --git a/java/com/android/dialer/commandline/impl/Blocking.java b/java/com/android/dialer/commandline/impl/Blocking.java new file mode 100644 index 000000000..2afd16522 --- /dev/null +++ b/java/com/android/dialer/commandline/impl/Blocking.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2018 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.dialer.commandline.impl; + +import android.content.Context; +import android.support.annotation.NonNull; +import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; +import com.android.dialer.commandline.Arguments; +import com.android.dialer.commandline.Command; +import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; +import com.android.dialer.inject.ApplicationContext; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import javax.inject.Inject; + +/** Block or unblock a number. */ +public class Blocking implements Command { + + @NonNull + @Override + public String getShortDescription() { + return "block or unblock numbers"; + } + + @NonNull + @Override + public String getUsage() { + return "blocking block|unblock|isblocked number\n\n" + "number should be e.164 formatted"; + } + + private final Context appContext; + private final ListeningExecutorService executorService; + + @Inject + Blocking( + @ApplicationContext Context context, + @BackgroundExecutor ListeningExecutorService executorService) { + this.appContext = context; + this.executorService = executorService; + } + + @Override + public ListenableFuture run(Arguments args) throws IllegalCommandLineArgumentException { + // AsyncQueryHandler must be created on a thread with looper. + // TODO(a bug): Use blocking version + FilteredNumberAsyncQueryHandler asyncQueryHandler = + new FilteredNumberAsyncQueryHandler(appContext); + return executorService.submit(() -> doInBackground(args, asyncQueryHandler)); + } + + private String doInBackground(Arguments args, FilteredNumberAsyncQueryHandler asyncQueryHandler) { + if (args.getPositionals().isEmpty()) { + return getUsage(); + } + + String command = args.getPositionals().get(0); + + if ("block".equals(command)) { + String number = args.getPositionals().get(1); + asyncQueryHandler.blockNumber((unused) -> {}, number, null); + return "blocked " + number; + } + + if ("unblock".equals(command)) { + String number = args.getPositionals().get(1); + Integer id = asyncQueryHandler.getBlockedIdSynchronous(number, null); + if (id == null) { + return number + " is not blocked"; + } + asyncQueryHandler.unblock((unusedRows, unusedValues) -> {}, id); + return "unblocked " + number; + } + + if ("isblocked".equals(command)) { + String number = args.getPositionals().get(1); + Integer id = asyncQueryHandler.getBlockedIdSynchronous(number, null); + return id == null ? "false" : "true"; + } + + return getUsage(); + } +} diff --git a/java/com/android/dialer/commandline/impl/Echo.java b/java/com/android/dialer/commandline/impl/Echo.java index b5f2f084b..2741a4042 100644 --- a/java/com/android/dialer/commandline/impl/Echo.java +++ b/java/com/android/dialer/commandline/impl/Echo.java @@ -19,8 +19,8 @@ package com.android.dialer.commandline.impl; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.text.TextUtils; +import com.android.dialer.commandline.Arguments; import com.android.dialer.commandline.Command; -import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import javax.inject.Inject; @@ -28,18 +28,24 @@ import javax.inject.Inject; /** Print arguments. */ public class Echo implements Command { - @VisibleForTesting - @Inject - public Echo() {} - + @NonNull @Override - public ListenableFuture run(ImmutableList args) { - return Futures.immediateFuture(TextUtils.join(" ", args)); + public String getShortDescription() { + return "@hide Print all arguments."; } @NonNull @Override - public String getShortDescription() { - return "@hide Print all arguments."; + public String getUsage() { + return "echo [arguments...]"; + } + + @VisibleForTesting + @Inject + public Echo() {} + + @Override + public ListenableFuture run(Arguments args) throws IllegalCommandLineArgumentException { + return Futures.immediateFuture(TextUtils.join(" ", args.getPositionals())); } } diff --git a/java/com/android/dialer/commandline/impl/Help.java b/java/com/android/dialer/commandline/impl/Help.java index d0e008014..357b10762 100644 --- a/java/com/android/dialer/commandline/impl/Help.java +++ b/java/com/android/dialer/commandline/impl/Help.java @@ -18,13 +18,14 @@ package com.android.dialer.commandline.impl; import android.content.Context; import android.support.annotation.NonNull; +import com.android.dialer.commandline.Arguments; import com.android.dialer.commandline.Command; import com.android.dialer.commandline.CommandLineComponent; import com.android.dialer.inject.ApplicationContext; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import java.util.Locale; import java.util.Map.Entry; import java.util.concurrent.ExecutionException; import javax.inject.Inject; @@ -32,6 +33,18 @@ import javax.inject.Inject; /** List available commands */ public class Help implements Command { + @NonNull + @Override + public String getShortDescription() { + return "Print this message"; + } + + @NonNull + @Override + public String getUsage() { + return "help"; + } + private final Context context; @Inject @@ -40,8 +53,8 @@ public class Help implements Command { } @Override - public ListenableFuture run(ImmutableList args) { - boolean showHidden = args.contains("--showHidden"); + public ListenableFuture run(Arguments args) throws IllegalCommandLineArgumentException { + boolean showHidden = args.getFlags().containsKey("showHidden"); StringBuilder stringBuilder = new StringBuilder(); ImmutableMap commands = @@ -59,20 +72,15 @@ public class Help implements Command { if (!showHidden && description.startsWith("@hide ")) { continue; } - stringBuilder - .append("\t") - .append(entry.getKey()) - .append("\t") - .append(description) - .append("\n"); + stringBuilder.append(String.format(Locale.US, " %20s %s\n", entry.getKey(), description)); } return Futures.immediateFuture(stringBuilder.toString()); } - private static String runOrThrow(Command command) { + private static String runOrThrow(Command command) throws IllegalCommandLineArgumentException { try { - return command.run(ImmutableList.of()).get(); + return command.run(Arguments.EMPTY).get(); } catch (InterruptedException e) { Thread.interrupted(); throw new RuntimeException(e); @@ -80,10 +88,4 @@ public class Help implements Command { throw new RuntimeException(e); } } - - @NonNull - @Override - public String getShortDescription() { - return "Print this message"; - } } diff --git a/java/com/android/dialer/commandline/impl/Version.java b/java/com/android/dialer/commandline/impl/Version.java index 5dfad9ae1..70476ea3e 100644 --- a/java/com/android/dialer/commandline/impl/Version.java +++ b/java/com/android/dialer/commandline/impl/Version.java @@ -20,9 +20,9 @@ import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.support.annotation.NonNull; +import com.android.dialer.commandline.Arguments; import com.android.dialer.commandline.Command; import com.android.dialer.inject.ApplicationContext; -import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import java.util.Locale; @@ -31,6 +31,18 @@ import javax.inject.Inject; /** Print the version name and code. */ public class Version implements Command { + @NonNull + @Override + public String getShortDescription() { + return "Print dialer version"; + } + + @NonNull + @Override + public String getUsage() { + return "version"; + } + private final Context appContext; @Inject @@ -39,7 +51,7 @@ public class Version implements Command { } @Override - public ListenableFuture run(ImmutableList args) { + public ListenableFuture run(Arguments args) throws IllegalCommandLineArgumentException { try { PackageInfo info = appContext.getPackageManager().getPackageInfo(appContext.getPackageName(), 0); @@ -49,10 +61,4 @@ public class Version implements Command { throw new RuntimeException(e); } } - - @NonNull - @Override - public String getShortDescription() { - return "Print dialer version"; - } } diff --git a/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java b/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java index e6c15e8d9..fa67feec8 100644 --- a/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java +++ b/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java @@ -29,6 +29,7 @@ import com.android.dialer.DialerPhoneNumber; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; +import com.android.dialer.common.concurrent.ThreadUtil; import com.android.dialer.common.database.Selection; import com.android.dialer.database.FilteredNumberContract.FilteredNumber; import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns; @@ -194,7 +195,7 @@ public final class DialerBlockedNumberPhoneLookup implements PhoneLookup Date: Mon, 22 Jan 2018 14:39:15 -0800 Subject: For call log entry labels, remove the dot after abbreviated time units. The platform utility adds a dot after time units (e.g., "8 min. ago") but we prefer not to have it (e.g., "8 min ago"). Bug: 70989595 Test: CallLogDatesTest, CallLogEntryTextTest PiperOrigin-RevId: 182838540 Change-Id: Ib8766a892aa04decb38917a6c5660804755725e0 --- java/com/android/dialer/calllogutils/CallLogDates.java | 14 +++++++++++--- java/com/android/dialer/calllogutils/CallLogEntryText.java | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/java/com/android/dialer/calllogutils/CallLogDates.java b/java/com/android/dialer/calllogutils/CallLogDates.java index bdf621518..fe3c0c3ad 100644 --- a/java/com/android/dialer/calllogutils/CallLogDates.java +++ b/java/com/android/dialer/calllogutils/CallLogDates.java @@ -36,7 +36,7 @@ public final class CallLogDates { * *

    *   if < 1 minute ago: "Just now";
-   *   else if < 1 hour ago: time relative to now (e.g., "8 min. ago");
+   *   else if < 1 hour ago: time relative to now (e.g., "8 min ago");
    *   else if today: time (e.g., "12:15 PM");
    *   else if < 7 days: abbreviated day of week (e.g., "Wed");
    *   else if < 1 year: date with abbreviated month, day, but no year (e.g., "Jan 15");
@@ -50,10 +50,18 @@ public final class CallLogDates {
       return context.getString(R.string.just_now);
     }
 
-    // For calls logged less than 1 hour ago, display time relative to now (e.g., "8 min. ago").
+    // For calls logged less than 1 hour ago, display time relative to now (e.g., "8 min ago").
     if (nowMillis - timestampMillis < TimeUnit.HOURS.toMillis(1)) {
       return DateUtils.getRelativeTimeSpanString(
-          timestampMillis, nowMillis, DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE);
+              timestampMillis,
+              nowMillis,
+              DateUtils.MINUTE_IN_MILLIS,
+              DateUtils.FORMAT_ABBREV_RELATIVE)
+          .toString()
+          // The platform method DateUtils#getRelativeTimeSpanString adds a dot ('.') after the
+          // abbreviated time unit for some languages (e.g., "8 min. ago") but we prefer not to have
+          // the dot.
+          .replace(".", "");
     }
 
     int dayDifference = getDayDifference(nowMillis, timestampMillis);
diff --git a/java/com/android/dialer/calllogutils/CallLogEntryText.java b/java/com/android/dialer/calllogutils/CallLogEntryText.java
index 25fe86452..a7a6bba9a 100644
--- a/java/com/android/dialer/calllogutils/CallLogEntryText.java
+++ b/java/com/android/dialer/calllogutils/CallLogEntryText.java
@@ -58,7 +58,7 @@ public final class CallLogEntryText {
    *
    *