blob: 1ffd9a11b300f698cfca8618e07611a9ac987603 [file] [log] [blame]
/*
* Copyright (C) 2022 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.annotation.NonNull;
import android.annotation.Nullable;
import android.app.time.LocationTimeZoneAlgorithmStatus;
import android.os.ShellCommand;
import android.os.SystemClock;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.StringTokenizer;
/**
* An event from the location_time_zone_manager service (AKA the location-based time zone detection
* algorithm). An event can represent a new time zone recommendation, an algorithm status change, or
* both.
*
* <p>Events have the following properties:
*
* <ul>
* <li>{@code algorithmStatus}: The current status of the location-based time zone detection
* algorithm.</li>
* <li>{@code suggestion}: The latest time zone suggestion, if there is one.</li>
* <li>{@code debugInfo} contains debugging metadata associated with the suggestion. This is
* used to record why the event exists and how information contained within it was obtained.
* This information exists only to aid in debugging and therefore is used by
* {@link #toString()}, but it is not for use in detection logic and is not considered in
* {@link #hashCode()} or {@link #equals(Object)}.
* </li>
* </ul>
*/
public final class LocationAlgorithmEvent {
@NonNull private final LocationTimeZoneAlgorithmStatus mAlgorithmStatus;
@Nullable private final GeolocationTimeZoneSuggestion mSuggestion;
@Nullable private ArrayList<String> mDebugInfo;
/** Creates a new instance. */
public LocationAlgorithmEvent(
@NonNull LocationTimeZoneAlgorithmStatus algorithmStatus,
@Nullable GeolocationTimeZoneSuggestion suggestion) {
mAlgorithmStatus = Objects.requireNonNull(algorithmStatus);
mSuggestion = suggestion;
}
/**
* Returns the status of the location time zone detector algorithm.
*/
@NonNull
public LocationTimeZoneAlgorithmStatus getAlgorithmStatus() {
return mAlgorithmStatus;
}
/**
* Returns the latest location algorithm suggestion. See {@link LocationAlgorithmEvent} for
* details.
*/
@Nullable
public GeolocationTimeZoneSuggestion getSuggestion() {
return mSuggestion;
}
/** Returns debug information. See {@link LocationAlgorithmEvent} for details. */
@NonNull
public List<String> getDebugInfo() {
return mDebugInfo == null
? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo);
}
/**
* Associates information with the instance that can be useful for debugging / logging. The
* information is present in {@link #toString()} but is not considered for
* {@link #equals(Object)} and {@link #hashCode()}.
*/
public void addDebugInfo(String... debugInfos) {
if (mDebugInfo == null) {
mDebugInfo = new ArrayList<>();
}
mDebugInfo.addAll(Arrays.asList(debugInfos));
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
LocationAlgorithmEvent that = (LocationAlgorithmEvent) o;
return mAlgorithmStatus.equals(that.mAlgorithmStatus)
&& Objects.equals(mSuggestion, that.mSuggestion);
}
@Override
public int hashCode() {
return Objects.hash(mAlgorithmStatus, mSuggestion);
}
@Override
public String toString() {
return "LocationAlgorithmEvent{"
+ "mAlgorithmStatus=" + mAlgorithmStatus
+ ", mSuggestion=" + mSuggestion
+ ", mDebugInfo=" + mDebugInfo
+ '}';
}
static LocationAlgorithmEvent parseCommandLineArg(@NonNull ShellCommand cmd) {
String suggestionString = null;
LocationTimeZoneAlgorithmStatus algorithmStatus = null;
String opt;
while ((opt = cmd.getNextArg()) != null) {
switch (opt) {
case "--status": {
algorithmStatus = LocationTimeZoneAlgorithmStatus.parseCommandlineArg(
cmd.getNextArgRequired());
break;
}
case "--suggestion": {
suggestionString = cmd.getNextArgRequired();
break;
}
default: {
throw new IllegalArgumentException("Unknown option: " + opt);
}
}
}
if (algorithmStatus == null) {
throw new IllegalArgumentException("Missing --status");
}
GeolocationTimeZoneSuggestion suggestion = null;
if (suggestionString != null) {
List<String> zoneIds = parseZoneIds(suggestionString);
long elapsedRealtimeMillis = SystemClock.elapsedRealtime();
if (zoneIds == null) {
suggestion = GeolocationTimeZoneSuggestion.createUncertainSuggestion(
elapsedRealtimeMillis);
} else {
suggestion = GeolocationTimeZoneSuggestion.createCertainSuggestion(
elapsedRealtimeMillis, zoneIds);
}
}
LocationAlgorithmEvent event = new LocationAlgorithmEvent(algorithmStatus, suggestion);
event.addDebugInfo("Command line injection");
return event;
}
private static List<String> parseZoneIds(String zoneIdsString) {
if ("UNCERTAIN".equals(zoneIdsString)) {
return null;
} else if ("EMPTY".equals(zoneIdsString)) {
return Collections.emptyList();
} else {
ArrayList<String> zoneIds = new ArrayList<>();
StringTokenizer tokenizer = new StringTokenizer(zoneIdsString, ",");
while (tokenizer.hasMoreTokens()) {
zoneIds.add(tokenizer.nextToken());
}
return zoneIds;
}
}
static void printCommandLineOpts(@NonNull PrintWriter pw) {
pw.println("Location algorithm event options:");
pw.println(" --status {LocationTimeZoneAlgorithmStatus toString() format}");
pw.println(" [--suggestion {UNCERTAIN|EMPTY|<Olson ID>+}]");
pw.println();
pw.println("See " + LocationAlgorithmEvent.class.getName() + " for more information");
}
}