1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
|
/*
* Copyright (C) 2017 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.timezone;
import android.annotation.IntDef;
import android.content.Context;
import android.os.Handler;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
/**
* The interface through which a time zone update application interacts with the Android system
* to handle time zone rule updates.
*
* <p>This interface is intended for use with the default APK-based time zone rules update
* application but it can also be used by OEMs if that mechanism is turned off using configuration.
* All callers must possess the {@link android.Manifest.permission#UPDATE_TIME_ZONE_RULES} system
* permission unless otherwise stated.
*
* <p>When using the default mechanism, when properly configured the Android system will send a
* {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent with a
* {@link RulesUpdaterContract#EXTRA_CHECK_TOKEN} extra to the time zone rules updater application
* when it detects that it or the OEM's APK containing time zone rules data has been modified. The
* updater application is then responsible for calling one of
* {@link #requestInstall(ParcelFileDescriptor, byte[], Callback)},
* {@link #requestUninstall(byte[], Callback)} or
* {@link #requestNothing(byte[], boolean)}, indicating, respectively, whether a new time zone rules
* distro should be installed, the current distro should be uninstalled, or there is nothing to do
* (or that the correct operation could not be determined due to an error). In each case the updater
* must pass the {@link RulesUpdaterContract#EXTRA_CHECK_TOKEN} value it received from the intent
* back so the system in the {@code checkToken} parameter.
*
* <p>If OEMs want to handle their own time zone rules updates, perhaps via a server-side component
* rather than an APK, then they should disable the default triggering mechanism in config and are
* responsible for triggering their own update checks / installs / uninstalls. In this case the
* "check token" parameter can be left null and there is never any need to call
* {@link #requestNothing(byte[], boolean)}.
*
* <p>OEMs should not mix the default mechanism and their own as this could lead to conflicts and
* unnecessary checks being triggered.
*
* <p>Applications obtain this using {@link android.app.Activity#getSystemService(String)} with
* {@link Context#TIME_ZONE_RULES_MANAGER_SERVICE}.
* @hide
*/
public final class RulesManager {
private static final String TAG = "timezone.RulesManager";
private static final boolean DEBUG = false;
/**
* The action of the intent that the Android system will broadcast when a time zone rules update
* operation has been successfully staged (i.e. to be applied next reboot) or unstaged.
*
* <p>See {@link #EXTRA_OPERATION_STAGED}
*
* <p>This is a protected intent that can only be sent by the system.
*/
public static final String ACTION_RULES_UPDATE_OPERATION =
"com.android.intent.action.timezone.RULES_UPDATE_OPERATION";
/**
* The key for a boolean extra for the {@link #ACTION_RULES_UPDATE_OPERATION} intent used to
* indicate whether the operation was a "stage" or an "unstage".
*/
public static final String EXTRA_OPERATION_STAGED = "staged";
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "SUCCESS", "ERROR_" }, value = {
SUCCESS,
ERROR_UNKNOWN_FAILURE,
ERROR_OPERATION_IN_PROGRESS
})
public @interface ResultCode {}
/**
* Indicates that an operation succeeded.
*/
public static final int SUCCESS = 0;
/**
* Indicates that an install/uninstall cannot be initiated because there is one already in
* progress.
*/
public static final int ERROR_OPERATION_IN_PROGRESS = 1;
/**
* Indicates an install / uninstall did not fully succeed for an unknown reason.
*/
public static final int ERROR_UNKNOWN_FAILURE = 2;
private final Context mContext;
private final IRulesManager mIRulesManager;
public RulesManager(Context context) {
mContext = context;
mIRulesManager = IRulesManager.Stub.asInterface(
ServiceManager.getService(Context.TIME_ZONE_RULES_MANAGER_SERVICE));
}
/**
* Returns information about the current time zone rules state such as the IANA version of
* the system and any currently installed distro. This method allows clients to determine the
* current device state, perhaps to see if it can be improved; for example by passing the
* information to a server that may provide a new distro for download.
*
* <p>Callers must possess the {@link android.Manifest.permission#QUERY_TIME_ZONE_RULES} system
* permission.
*/
public RulesState getRulesState() {
try {
logDebug("mIRulesManager.getRulesState()");
RulesState rulesState = mIRulesManager.getRulesState();
logDebug("mIRulesManager.getRulesState() returned " + rulesState);
return rulesState;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Requests installation of the supplied distro. The distro must have been checked for integrity
* by the caller or have been received via a trusted mechanism.
*
* @param distroFileDescriptor the file descriptor for the distro
* @param checkToken an optional token provided if the install was triggered in response to a
* {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
* @param callback the {@link Callback} to receive callbacks related to the installation
* @return {@link #SUCCESS} if the installation will be attempted
*/
@ResultCode
public int requestInstall(
ParcelFileDescriptor distroFileDescriptor, byte[] checkToken, Callback callback)
throws IOException {
ICallback iCallback = new CallbackWrapper(mContext, callback);
try {
logDebug("mIRulesManager.requestInstall()");
return mIRulesManager.requestInstall(distroFileDescriptor, checkToken, iCallback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Requests uninstallation of the currently installed distro (leaving the device with no
* distro installed).
*
* @param checkToken an optional token provided if the uninstall was triggered in response to a
* {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
* @param callback the {@link Callback} to receive callbacks related to the uninstall
* @return {@link #SUCCESS} if the uninstallation will be attempted
*/
@ResultCode
public int requestUninstall(byte[] checkToken, Callback callback) {
ICallback iCallback = new CallbackWrapper(mContext, callback);
try {
logDebug("mIRulesManager.requestUninstall()");
return mIRulesManager.requestUninstall(checkToken, iCallback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/*
* We wrap incoming binder calls with a private class implementation that
* redirects them into main-thread actions. This serializes the backup
* progress callbacks nicely within the usual main-thread lifecycle pattern.
*/
private class CallbackWrapper extends ICallback.Stub {
final Handler mHandler;
final Callback mCallback;
CallbackWrapper(Context context, Callback callback) {
mCallback = callback;
mHandler = new Handler(context.getMainLooper());
}
// Binder calls into this object just enqueue on the main-thread handler
@Override
public void onFinished(int status) {
logDebug("mCallback.onFinished(status), status=" + status);
mHandler.post(() -> mCallback.onFinished(status));
}
}
/**
* Requests the system does not modify the currently installed time zone distro, if any. This
* method records the fact that a time zone check operation triggered by the system is now
* complete and there was nothing to do. The token passed should be the one presented when the
* check was triggered.
*
* <p>Note: Passing {@code success == false} may result in more checks being triggered. Clients
* should be careful not to pass false if the failure is unlikely to resolve by itself.
*
* @param checkToken an optional token provided if the install was triggered in response to a
* {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
* @param succeeded true if the check was successful, false if it was not successful but may
* succeed if it is retried
*/
public void requestNothing(byte[] checkToken, boolean succeeded) {
try {
logDebug("mIRulesManager.requestNothing() with token=" + Arrays.toString(checkToken));
mIRulesManager.requestNothing(checkToken, succeeded);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
static void logDebug(String msg) {
if (DEBUG) {
Log.v(TAG, msg);
}
}
}
|