Merge "Add command line support for testing tz detection"

This commit is contained in:
Neil Fuller
2020-03-05 18:07:24 +00:00
committed by Gerrit Code Review
7 changed files with 416 additions and 0 deletions

View File

@@ -20,7 +20,9 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.ShellCommand;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -127,4 +129,32 @@ public final class ManualTimeZoneSuggestion implements Parcelable {
+ ", mDebugInfo=" + mDebugInfo
+ '}';
}
/** @hide */
public static ManualTimeZoneSuggestion parseCommandLineArg(@NonNull ShellCommand cmd) {
String zoneId = null;
String opt;
while ((opt = cmd.getNextArg()) != null) {
switch (opt) {
case "--zone_id": {
zoneId = cmd.getNextArgRequired();
break;
}
default: {
throw new IllegalArgumentException("Unknown option: " + opt);
}
}
}
ManualTimeZoneSuggestion suggestion = new ManualTimeZoneSuggestion(zoneId);
suggestion.addDebugInfo("Command line injection");
return suggestion;
}
/** @hide */
public static void printCommandLineOpts(@NonNull PrintWriter pw) {
pw.println("Manual suggestion options:");
pw.println(" --zone_id <Olson ID>");
pw.println();
pw.println("See " + ManualTimeZoneSuggestion.class.getName() + " for more information");
}
}

View File

@@ -21,7 +21,10 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.ShellCommand;
import android.text.TextUtils;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -392,4 +395,96 @@ public final class TelephonyTimeZoneSuggestion implements Parcelable {
return new TelephonyTimeZoneSuggestion(this);
}
}
/** @hide */
public static TelephonyTimeZoneSuggestion parseCommandLineArg(@NonNull ShellCommand cmd)
throws IllegalArgumentException {
Integer slotIndex = null;
String zoneId = null;
Integer quality = null;
Integer matchType = null;
String opt;
while ((opt = cmd.getNextArg()) != null) {
switch (opt) {
case "--slot_index": {
slotIndex = Integer.parseInt(cmd.getNextArgRequired());
break;
}
case "--zone_id": {
zoneId = cmd.getNextArgRequired();
break;
}
case "--quality": {
quality = parseQualityCommandLineArg(cmd.getNextArgRequired());
break;
}
case "--match_type": {
matchType = parseMatchTypeCommandLineArg(cmd.getNextArgRequired());
break;
}
default: {
throw new IllegalArgumentException("Unknown option: " + opt);
}
}
}
if (slotIndex == null) {
throw new IllegalArgumentException("No slotIndex specified.");
}
Builder builder = new Builder(slotIndex);
if (!(TextUtils.isEmpty(zoneId) || "_".equals(zoneId))) {
builder.setZoneId(zoneId);
}
if (quality != null) {
builder.setQuality(quality);
}
if (matchType != null) {
builder.setMatchType(matchType);
}
builder.addDebugInfo("Command line injection");
return builder.build();
}
private static int parseQualityCommandLineArg(@NonNull String arg) {
switch (arg) {
case "single":
return QUALITY_SINGLE_ZONE;
case "multiple_same":
return QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
case "multiple_different":
return QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
default:
throw new IllegalArgumentException("Unrecognized quality: " + arg);
}
}
private static int parseMatchTypeCommandLineArg(@NonNull String arg) {
switch (arg) {
case "emulator":
return MATCH_TYPE_EMULATOR_ZONE_ID;
case "country_with_offset":
return MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET;
case "country":
return MATCH_TYPE_NETWORK_COUNTRY_ONLY;
case "test_network":
return MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY;
default:
throw new IllegalArgumentException("Unrecognized match_type: " + arg);
}
}
/** @hide */
public static void printCommandLineOpts(@NonNull PrintWriter pw) {
pw.println("Telephony suggestion options:");
pw.println(" --slot_index <number>");
pw.println(" To withdraw a previous suggestion:");
pw.println(" [--zone_id \"_\"]");
pw.println(" To make a new suggestion:");
pw.println(" --zone_id <Olson ID>");
pw.println(" --quality <single|multiple_same|multiple_different>");
pw.println(" --match_type <emulator|country_with_offset|country|test_network>");
pw.println();
pw.println("See " + TelephonyTimeZoneSuggestion.class.getName() + " for more information");
}
}

View File

@@ -18,12 +18,19 @@ package android.app.timezonedetector;
import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import android.os.ShellCommand;
import org.junit.Test;
import java.io.PrintWriter;
import java.io.StringWriter;
public class ManualTimeZoneSuggestionTest {
private static final String ARBITRARY_ZONE_ID1 = "Europe/London";
@@ -58,4 +65,36 @@ public class ManualTimeZoneSuggestionTest {
ManualTimeZoneSuggestion rtSuggestion = roundTripParcelable(suggestion);
assertEquals(suggestion.getDebugInfo(), rtSuggestion.getDebugInfo());
}
@Test
public void testPrintCommandLineOpts() throws Exception {
try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
ManualTimeZoneSuggestion.printCommandLineOpts(pw);
assertTrue(sw.getBuffer().length() > 0);
}
}
@Test(expected = IllegalArgumentException.class)
public void testParseCommandLineArg_noArgs() {
ShellCommand testShellCommand = createShellCommandWithArgsAndOptions("");
ManualTimeZoneSuggestion.parseCommandLineArg(testShellCommand);
}
@Test
public void testParseCommandLineArg_validSuggestion() {
ShellCommand testShellCommand =
createShellCommandWithArgsAndOptions("--zone_id Europe/London");
ManualTimeZoneSuggestion expectedSuggestion =
new ManualTimeZoneSuggestion("Europe/London");
ManualTimeZoneSuggestion actualSuggestion =
ManualTimeZoneSuggestion.parseCommandLineArg(testShellCommand);
assertEquals(expectedSuggestion, actualSuggestion);
}
@Test(expected = IllegalArgumentException.class)
public void testParseCommandLineArg_unknownArgument() {
ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
"--zone_id Europe/London --bad_arg 0");
ManualTimeZoneSuggestion.parseCommandLineArg(testShellCommand);
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2020 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 android.app.timezonedetector;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.os.ShellCommand;
import org.mockito.stubbing.Answer;
import java.util.Arrays;
import java.util.List;
/** Utility methods related to {@link ShellCommand} objects used in several tests. */
final class ShellCommandTestSupport {
private ShellCommandTestSupport() {}
static ShellCommand createShellCommandWithArgsAndOptions(String argsWithSpaces) {
return createShellCommandWithArgsAndOptions(Arrays.asList(argsWithSpaces.split(" ")));
}
static ShellCommand createShellCommandWithArgsAndOptions(List<String> args) {
ShellCommand command = mock(ShellCommand.class);
class ArgProvider {
private int mCount;
String getNext() {
if (mCount >= args.size()) {
return null;
}
return args.get(mCount++);
}
String getNextRequired() {
String next = getNext();
if (next == null) {
throw new IllegalArgumentException("No next");
}
return next;
}
}
ArgProvider argProvider = new ArgProvider();
when(command.getNextArg()).thenAnswer(
(Answer<String>) invocation -> argProvider.getNext());
when(command.getNextOption()).thenAnswer(
(Answer<String>) invocation -> argProvider.getNext());
when(command.getNextArgRequired()).thenAnswer(
(Answer<String>) invocation -> argProvider.getNextRequired());
return command;
}
}

View File

@@ -18,13 +18,19 @@ package android.app.timezonedetector;
import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import android.os.ShellCommand;
import org.junit.Test;
import java.io.PrintWriter;
import java.io.StringWriter;
public class TelephonyTimeZoneSuggestionTest {
private static final int SLOT_INDEX = 99999;
@@ -159,4 +165,57 @@ public class TelephonyTimeZoneSuggestionTest {
assertEquals(suggestion1, suggestion1_2);
assertTrue(suggestion1_2.getDebugInfo().contains(debugString));
}
@Test
public void testPrintCommandLineOpts() throws Exception {
try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
TelephonyTimeZoneSuggestion.printCommandLineOpts(pw);
assertTrue(sw.getBuffer().length() > 0);
}
}
@Test(expected = IllegalArgumentException.class)
public void testParseCommandLineArg_noArgs() {
ShellCommand testShellCommand = createShellCommandWithArgsAndOptions("");
TelephonyTimeZoneSuggestion.parseCommandLineArg(testShellCommand);
}
@Test(expected = IllegalArgumentException.class)
public void testParseCommandLineArg_noSlotIndex() {
ShellCommand testShellCommand = createShellCommandWithArgsAndOptions("--zone_id _");
TelephonyTimeZoneSuggestion.parseCommandLineArg(testShellCommand);
}
@Test
public void testParseCommandLineArg_validEmptyZoneIdSuggestion() {
ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
"--slot_index 0 --zone_id _");
TelephonyTimeZoneSuggestion expectedSuggestion =
new TelephonyTimeZoneSuggestion.Builder(0).build();
TelephonyTimeZoneSuggestion actualSuggestion =
TelephonyTimeZoneSuggestion.parseCommandLineArg(testShellCommand);
assertEquals(expectedSuggestion, actualSuggestion);
}
@Test
public void testParseCommandLineArg_validNonEmptySuggestion() {
ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
"--slot_index 0 --zone_id Europe/London --quality single --match_type country");
TelephonyTimeZoneSuggestion expectedSuggestion =
new TelephonyTimeZoneSuggestion.Builder(0)
.setZoneId("Europe/London")
.setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE)
.setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY)
.build();
TelephonyTimeZoneSuggestion actualSuggestion =
TelephonyTimeZoneSuggestion.parseCommandLineArg(testShellCommand);
assertEquals(expectedSuggestion, actualSuggestion);
}
@Test(expected = IllegalArgumentException.class)
public void testParseCommandLineArg_unknownArgument() {
ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
"--slot_index 0 --zone_id _ --bad_arg 0");
TelephonyTimeZoneSuggestion.parseCommandLineArg(testShellCommand);
}
}

View File

@@ -25,6 +25,8 @@ import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.provider.Settings;
import com.android.internal.annotations.VisibleForTesting;
@@ -133,5 +135,13 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE,
"suggest manual time and time zone");
}
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
(new TimeZoneDetectorShellCommand(this)).exec(
this, in, out, err, args, callback, resultReceiver);
}
}

View File

@@ -0,0 +1,118 @@
/*
* Copyright (C) 2020 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.server.timezonedetector;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.os.ShellCommand;
import java.io.PrintWriter;
/** Implemented the shell command interface for {@link TimeZoneDetectorService}. */
class TimeZoneDetectorShellCommand extends ShellCommand {
private final TimeZoneDetectorService mInterface;
TimeZoneDetectorShellCommand(TimeZoneDetectorService timeZoneDetectorService) {
mInterface = timeZoneDetectorService;
}
@Override
public int onCommand(String cmd) {
if (cmd == null) {
return handleDefaultCommands(cmd);
}
switch (cmd) {
case "suggestTelephonyTimeZone":
return runSuggestTelephonyTimeZone();
case "suggestManualTimeZone":
return runSuggestManualTimeZone();
default: {
return handleDefaultCommands(cmd);
}
}
}
private int runSuggestTelephonyTimeZone() {
final PrintWriter pw = getOutPrintWriter();
try {
TelephonyTimeZoneSuggestion suggestion = null;
String opt;
while ((opt = getNextArg()) != null) {
if ("--suggestion".equals(opt)) {
suggestion = TelephonyTimeZoneSuggestion.parseCommandLineArg(this);
} else {
pw.println("Error: Unknown option: " + opt);
return 1;
}
}
if (suggestion == null) {
pw.println("Error: suggestion not specified");
return 1;
}
mInterface.suggestTelephonyTimeZone(suggestion);
pw.println("Suggestion " + suggestion + " injected.");
return 0;
} catch (RuntimeException e) {
pw.println(e.toString());
return 1;
}
}
private int runSuggestManualTimeZone() {
final PrintWriter pw = getOutPrintWriter();
try {
ManualTimeZoneSuggestion suggestion = null;
String opt;
while ((opt = getNextArg()) != null) {
if ("--suggestion".equals(opt)) {
suggestion = ManualTimeZoneSuggestion.parseCommandLineArg(this);
} else {
pw.println("Error: Unknown option: " + opt);
return 1;
}
}
if (suggestion == null) {
pw.println("Error: suggestion not specified");
return 1;
}
mInterface.suggestManualTimeZone(suggestion);
pw.println("Suggestion " + suggestion + " injected.");
return 0;
} catch (RuntimeException e) {
pw.println(e.toString());
return 1;
}
}
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
pw.println("Time Zone Detector (time_zone_detector) commands:");
pw.println(" help");
pw.println(" Print this help text.");
pw.println(" suggestTelephonyTimeZone");
pw.println(" --suggestion <telephony suggestion opts>");
pw.println(" suggestManualTimeZone");
pw.println(" --suggestion <manual suggestion opts>");
pw.println();
ManualTimeZoneSuggestion.printCommandLineOpts(pw);
pw.println();
TelephonyTimeZoneSuggestion.printCommandLineOpts(pw);
pw.println();
}
}