1 /* 2 * Copyright (C) 2020 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 com.android.server.timezonedetector.location; 18 19 import static android.Manifest.permission.BIND_TIME_ZONE_PROVIDER_SERVICE; 20 import static android.Manifest.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE; 21 import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY; 22 import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY; 23 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.content.Context; 27 import android.os.Bundle; 28 import android.os.Handler; 29 import android.os.IBinder; 30 import android.os.RemoteCallback; 31 import android.service.timezone.ITimeZoneProvider; 32 import android.service.timezone.ITimeZoneProviderManager; 33 import android.service.timezone.TimeZoneProviderSuggestion; 34 import android.util.IndentingPrintWriter; 35 36 import com.android.internal.annotations.GuardedBy; 37 import com.android.server.servicewatcher.CurrentUserServiceSupplier; 38 import com.android.server.servicewatcher.CurrentUserServiceSupplier.BoundServiceInfo; 39 import com.android.server.servicewatcher.ServiceWatcher; 40 import com.android.server.servicewatcher.ServiceWatcher.ServiceListener; 41 42 import java.util.Objects; 43 44 /** 45 * System server-side proxy for ITimeZoneProvider implementations, i.e. this provides the 46 * system server object used to communicate with a remote {@link 47 * android.service.timezone.TimeZoneProviderService} over Binder, which could be running in a 48 * different process. As "remote" providers are bound / unbound this proxy will rebind to the "best" 49 * available remote process. 50 */ 51 class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy implements 52 ServiceListener<BoundServiceInfo> { 53 54 @NonNull private final ServiceWatcher mServiceWatcher; 55 56 @GuardedBy("mSharedLock") 57 @Nullable private ManagerProxy mManagerProxy; 58 59 @GuardedBy("mSharedLock") 60 @NonNull private TimeZoneProviderRequest mRequest; 61 RealLocationTimeZoneProviderProxy( @onNull Context context, @NonNull Handler handler, @NonNull ThreadingDomain threadingDomain, @NonNull String action, @NonNull String providerPackageName)62 RealLocationTimeZoneProviderProxy( 63 @NonNull Context context, @NonNull Handler handler, 64 @NonNull ThreadingDomain threadingDomain, @NonNull String action, 65 @NonNull String providerPackageName) { 66 super(context, threadingDomain); 67 mManagerProxy = null; 68 mRequest = TimeZoneProviderRequest.createStopUpdatesRequest(); 69 70 Objects.requireNonNull(providerPackageName); 71 mServiceWatcher = ServiceWatcher.create(context, 72 handler, 73 "RealLocationTimeZoneProviderProxy", 74 new CurrentUserServiceSupplier(context, action, 75 providerPackageName, BIND_TIME_ZONE_PROVIDER_SERVICE, 76 INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE), 77 this); 78 } 79 80 @Override onInitialize()81 void onInitialize() { 82 if (!register()) { 83 throw new IllegalStateException("Unable to register binder proxy"); 84 } 85 } 86 87 @Override onDestroy()88 void onDestroy() { 89 mServiceWatcher.unregister(); 90 } 91 register()92 private boolean register() { 93 boolean resolves = mServiceWatcher.checkServiceResolves(); 94 if (resolves) { 95 mServiceWatcher.register(); 96 } 97 return resolves; 98 } 99 100 @Override onBind(IBinder binder, BoundServiceInfo boundService)101 public void onBind(IBinder binder, BoundServiceInfo boundService) { 102 mThreadingDomain.assertCurrentThread(); 103 104 synchronized (mSharedLock) { 105 // When a new remote is first bound we create the ManagerProxy that will be passed to 106 // it. By creating a new one for each bind the ManagerProxy can check whether it is 107 // still the current proxy and if not it can ignore incoming calls. 108 mManagerProxy = new ManagerProxy(); 109 mListener.onProviderBound(); 110 111 // Send the current request to the remote. 112 trySendCurrentRequest(); 113 } 114 } 115 116 @Override onUnbind()117 public void onUnbind() { 118 mThreadingDomain.assertCurrentThread(); 119 120 synchronized (mSharedLock) { 121 // Clear the ManagerProxy used with the old remote so we will ignore calls from any old 122 // remotes that somehow hold a reference to it. 123 mManagerProxy = null; 124 mListener.onProviderUnbound(); 125 } 126 } 127 128 @Override setRequest(@onNull TimeZoneProviderRequest request)129 final void setRequest(@NonNull TimeZoneProviderRequest request) { 130 mThreadingDomain.assertCurrentThread(); 131 132 Objects.requireNonNull(request); 133 synchronized (mSharedLock) { 134 mRequest = request; 135 136 // Two possible outcomes here: Either we are already bound to a remote service, in 137 // which case trySendCurrentRequest() will communicate the request immediately, or we 138 // are not bound to the remote service yet, in which case it will be sent during 139 // onBindOnHandlerThread() instead. 140 trySendCurrentRequest(); 141 } 142 } 143 144 @GuardedBy("mSharedLock") trySendCurrentRequest()145 private void trySendCurrentRequest() { 146 ManagerProxy managerProxy = mManagerProxy; 147 TimeZoneProviderRequest request = mRequest; 148 mServiceWatcher.runOnBinder(binder -> { 149 ITimeZoneProvider service = ITimeZoneProvider.Stub.asInterface(binder); 150 if (request.sendUpdates()) { 151 service.startUpdates(managerProxy, request.getInitializationTimeout().toMillis()); 152 } else { 153 service.stopUpdates(); 154 } 155 }); 156 } 157 158 /** 159 * A stubbed implementation. 160 */ 161 @Override handleTestCommand(@onNull TestCommand testCommand, @Nullable RemoteCallback callback)162 void handleTestCommand(@NonNull TestCommand testCommand, @Nullable RemoteCallback callback) { 163 mThreadingDomain.assertCurrentThread(); 164 165 if (callback != null) { 166 Bundle result = new Bundle(); 167 result.putBoolean(TEST_COMMAND_RESULT_SUCCESS_KEY, false); 168 result.putString(TEST_COMMAND_RESULT_ERROR_KEY, "Not implemented"); 169 callback.sendResult(result); 170 } 171 } 172 173 @Override dump(@onNull IndentingPrintWriter ipw, @Nullable String[] args)174 public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) { 175 synchronized (mSharedLock) { 176 ipw.println("{RealLocationTimeZoneProviderProxy}"); 177 ipw.println("mRequest=" + mRequest); 178 mServiceWatcher.dump(ipw); 179 } 180 } 181 182 /** 183 * A system Server-side proxy for the ITimeZoneProviderManager, i.e. this is a local binder stub 184 * Each "remote" TimeZoneProvider is passed a binder instance that it then uses to communicate 185 * back with the system server, invoking the logic here. 186 */ 187 private class ManagerProxy extends ITimeZoneProviderManager.Stub { 188 189 // executed on binder thread 190 @Override onTimeZoneProviderSuggestion(TimeZoneProviderSuggestion suggestion)191 public void onTimeZoneProviderSuggestion(TimeZoneProviderSuggestion suggestion) { 192 onTimeZoneProviderEvent(TimeZoneProviderEvent.createSuggestionEvent(suggestion)); 193 } 194 195 // executed on binder thread 196 @Override onTimeZoneProviderUncertain()197 public void onTimeZoneProviderUncertain() { 198 onTimeZoneProviderEvent(TimeZoneProviderEvent.createUncertainEvent()); 199 200 } 201 202 // executed on binder thread 203 @Override onTimeZoneProviderPermanentFailure(String failureReason)204 public void onTimeZoneProviderPermanentFailure(String failureReason) { 205 onTimeZoneProviderEvent( 206 TimeZoneProviderEvent.createPermanentFailureEvent(failureReason)); 207 } 208 onTimeZoneProviderEvent(TimeZoneProviderEvent event)209 private void onTimeZoneProviderEvent(TimeZoneProviderEvent event) { 210 synchronized (mSharedLock) { 211 if (mManagerProxy != this) { 212 // Ignore incoming calls if this instance is no longer the current 213 // mManagerProxy. 214 return; 215 } 216 } 217 handleTimeZoneProviderEvent(event); 218 } 219 } 220 } 221