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