Loading core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java +1 −1 Original line number Original line Diff line number Diff line Loading @@ -117,7 +117,7 @@ public final class TimeZoneDetectorImpl implements TimeZoneDetector { } } private void notifyConfigurationListeners(@NonNull TimeZoneConfiguration configuration) { private void notifyConfigurationListeners(@NonNull TimeZoneConfiguration configuration) { ArraySet<TimeZoneConfigurationListener> configurationListeners; final ArraySet<TimeZoneConfigurationListener> configurationListeners; synchronized (this) { synchronized (this) { configurationListeners = new ArraySet<>(mConfigurationListeners); configurationListeners = new ArraySet<>(mConfigurationListeners); } } Loading services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java +85 −89 Original line number Original line Diff line number Diff line Loading @@ -18,7 +18,6 @@ package com.android.server.timezonedetector; import android.annotation.NonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.timezonedetector.ITimeZoneConfigurationListener; import android.app.timezonedetector.ITimeZoneConfigurationListener; import android.app.timezonedetector.ITimeZoneDetectorService; import android.app.timezonedetector.ITimeZoneDetectorService; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.ManualTimeZoneSuggestion; Loading @@ -38,6 +37,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings; import android.util.IndentingPrintWriter; import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting; Loading @@ -60,7 +60,8 @@ import java.util.Objects; * and making calls async, leaving the (consequently more testable) {@link TimeZoneDetectorStrategy} * and making calls async, leaving the (consequently more testable) {@link TimeZoneDetectorStrategy} * implementation to deal with the logic around time zone detection. * implementation to deal with the logic around time zone detection. */ */ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub { public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub implements IBinder.DeathRecipient { private static final String TAG = "TimeZoneDetectorService"; private static final String TAG = "TimeZoneDetectorService"; Loading Loading @@ -104,9 +105,15 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub @NonNull @NonNull private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy; private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy; /** * This sparse array acts as a map from userId to listeners running as that userId. User scoped * as time zone detection configuration is partially user-specific, so different users can * get different configuration. */ @GuardedBy("mConfigurationListeners") @GuardedBy("mConfigurationListeners") @NonNull @NonNull private final ArrayList<ConfigListenerInfo> mConfigurationListeners = new ArrayList<>(); private final SparseArray<ArrayList<ITimeZoneConfigurationListener>> mConfigurationListeners = new SparseArray<>(); private static TimeZoneDetectorService create( private static TimeZoneDetectorService create( @NonNull Context context, @NonNull Handler handler, @NonNull Context context, @NonNull Handler handler, Loading Loading @@ -188,18 +195,23 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub Objects.requireNonNull(listener); Objects.requireNonNull(listener); int userId = UserHandle.getCallingUserId(); int userId = UserHandle.getCallingUserId(); ConfigListenerInfo listenerInfo = new ConfigListenerInfo(userId, listener); synchronized (mConfigurationListeners) { synchronized (mConfigurationListeners) { if (mConfigurationListeners.contains(listenerInfo)) { ArrayList<ITimeZoneConfigurationListener> listeners = mConfigurationListeners.get(userId); if (listeners != null && listeners.contains(listener)) { return; return; } } try { try { // Ensure the reference to the listener is removed if the client process dies. if (listeners == null) { listenerInfo.linkToDeath(); listeners = new ArrayList<>(1); mConfigurationListeners.put(userId, listeners); } // Ensure the reference to the listener will be removed if the client process dies. listener.asBinder().linkToDeath(this, 0 /* flags */); // Only add the listener if we can linkToDeath(). // Only add the listener if we can linkToDeath(). mConfigurationListeners.add(listenerInfo); listeners.add(listener); } catch (RemoteException e) { } catch (RemoteException e) { Slog.e(TAG, "Unable to linkToDeath() for listener=" + listener, e); Slog.e(TAG, "Unable to linkToDeath() for listener=" + listener, e); } } Loading @@ -213,22 +225,57 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub int userId = UserHandle.getCallingUserId(); int userId = UserHandle.getCallingUserId(); synchronized (mConfigurationListeners) { synchronized (mConfigurationListeners) { ConfigListenerInfo toRemove = new ConfigListenerInfo(userId, listener); boolean removedListener = false; Iterator<ConfigListenerInfo> listenerIterator = mConfigurationListeners.iterator(); ArrayList<ITimeZoneConfigurationListener> userListeners = while (listenerIterator.hasNext()) { mConfigurationListeners.get(userId); ConfigListenerInfo currentListenerInfo = listenerIterator.next(); if (userListeners.remove(listener)) { if (currentListenerInfo.equals(toRemove)) { listenerIterator.remove(); // Stop listening for the client process to die. // Stop listening for the client process to die. try { listener.asBinder().unlinkToDeath(this, 0 /* flags */); currentListenerInfo.unlinkToDeath(); removedListener = true; } catch (RemoteException e) { Slog.e(TAG, "Unable to unlinkToDeath() for listener=" + listener, e); } } if (!removedListener) { Slog.w(TAG, "Client asked to remove listenener=" + listener + ", but no listeners were removed." + " mConfigurationListeners=" + mConfigurationListeners); } } } } } } @Override public void binderDied() { // Should not be used as binderDied(IBinder who) is overridden. Slog.wtf(TAG, "binderDied() called unexpectedly."); } /** * Called when one of the ITimeZoneConfigurationListener processes dies before calling * {@link #removeConfigurationListener(ITimeZoneConfigurationListener)}. */ @Override public void binderDied(IBinder who) { synchronized (mConfigurationListeners) { boolean removedListener = false; final int userCount = mConfigurationListeners.size(); for (int i = 0; i < userCount; i++) { ArrayList<ITimeZoneConfigurationListener> userListeners = mConfigurationListeners.valueAt(i); Iterator<ITimeZoneConfigurationListener> userListenerIterator = userListeners.iterator(); while (userListenerIterator.hasNext()) { ITimeZoneConfigurationListener userListener = userListenerIterator.next(); if (userListener.asBinder().equals(who)) { userListenerIterator.remove(); removedListener = true; break; } } } if (!removedListener) { Slog.w(TAG, "Notified of binder death for who=" + who + ", but did not remove any listeners." + " mConfigurationListeners=" + mConfigurationListeners); } } } } void handleConfigurationChanged() { void handleConfigurationChanged() { Loading @@ -243,14 +290,24 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub // problem. // problem. synchronized (mConfigurationListeners) { synchronized (mConfigurationListeners) { for (ConfigListenerInfo listenerInfo : mConfigurationListeners) { final int userCount = mConfigurationListeners.size(); for (int userIndex = 0; userIndex < userCount; userIndex++) { int userId = mConfigurationListeners.keyAt(userIndex); TimeZoneConfiguration configuration = TimeZoneConfiguration configuration = mTimeZoneDetectorStrategy.getConfiguration(listenerInfo.getUserId()); mTimeZoneDetectorStrategy.getConfiguration(userId); ArrayList<ITimeZoneConfigurationListener> listeners = mConfigurationListeners.valueAt(userIndex); final int listenerCount = listeners.size(); for (int listenerIndex = 0; listenerIndex < listenerCount; listenerIndex++) { ITimeZoneConfigurationListener listener = listeners.get(listenerIndex); try { try { listenerInfo.getListener().onChange(configuration); listener.onChange(configuration); } catch (RemoteException e) { } catch (RemoteException e) { Slog.w(TAG, "Unable to notify listener=" Slog.w(TAG, "Unable to notify listener=" + listener + listenerInfo + " of updated configuration=" + configuration, e); + " for userId=" + userId + " of updated configuration=" + configuration, e); } } } } } } } Loading Loading @@ -338,66 +395,5 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub (new TimeZoneDetectorShellCommand(this)).exec( (new TimeZoneDetectorShellCommand(this)).exec( this, in, out, err, args, callback, resultReceiver); this, in, out, err, args, callback, resultReceiver); } } private class ConfigListenerInfo implements IBinder.DeathRecipient { private final @UserIdInt int mUserId; private final ITimeZoneConfigurationListener mListener; ConfigListenerInfo( @UserIdInt int userId, @NonNull ITimeZoneConfigurationListener listener) { this.mUserId = userId; this.mListener = Objects.requireNonNull(listener); } @UserIdInt int getUserId() { return mUserId; } ITimeZoneConfigurationListener getListener() { return mListener; } void linkToDeath() throws RemoteException { mListener.asBinder().linkToDeath(this, 0 /* flags */); } void unlinkToDeath() throws RemoteException { mListener.asBinder().unlinkToDeath(this, 0 /* flags */); } @Override public void binderDied() { synchronized (mConfigurationListeners) { Slog.i(TAG, "Configuration listener client died: " + this); mConfigurationListeners.remove(this); } } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ConfigListenerInfo that = (ConfigListenerInfo) o; return mUserId == that.mUserId && mListener.equals(that.mListener); } @Override public int hashCode() { return Objects.hash(mUserId, mListener); } @Override public String toString() { return "ConfigListenerInfo{" + "mUserId=" + mUserId + ", mListener=" + mListener + '}'; } } } } services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java +1 −1 Original line number Original line Diff line number Diff line Loading @@ -170,7 +170,7 @@ public class TimeZoneDetectorServiceTest { ITimeZoneConfigurationListener mockListener = mock(ITimeZoneConfigurationListener.class); ITimeZoneConfigurationListener mockListener = mock(ITimeZoneConfigurationListener.class); try { try { mTimeZoneDetectorService.removeConfigurationListener(mockListener); mTimeZoneDetectorService.removeConfigurationListener(mockListener); fail(); fail("Expected a SecurityException"); } finally { } finally { verify(mMockContext).enforceCallingPermission( verify(mMockContext).enforceCallingPermission( eq(android.Manifest.permission.WRITE_SECURE_SETTINGS), eq(android.Manifest.permission.WRITE_SECURE_SETTINGS), Loading Loading
core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java +1 −1 Original line number Original line Diff line number Diff line Loading @@ -117,7 +117,7 @@ public final class TimeZoneDetectorImpl implements TimeZoneDetector { } } private void notifyConfigurationListeners(@NonNull TimeZoneConfiguration configuration) { private void notifyConfigurationListeners(@NonNull TimeZoneConfiguration configuration) { ArraySet<TimeZoneConfigurationListener> configurationListeners; final ArraySet<TimeZoneConfigurationListener> configurationListeners; synchronized (this) { synchronized (this) { configurationListeners = new ArraySet<>(mConfigurationListeners); configurationListeners = new ArraySet<>(mConfigurationListeners); } } Loading
services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java +85 −89 Original line number Original line Diff line number Diff line Loading @@ -18,7 +18,6 @@ package com.android.server.timezonedetector; import android.annotation.NonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.timezonedetector.ITimeZoneConfigurationListener; import android.app.timezonedetector.ITimeZoneConfigurationListener; import android.app.timezonedetector.ITimeZoneDetectorService; import android.app.timezonedetector.ITimeZoneDetectorService; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.ManualTimeZoneSuggestion; Loading @@ -38,6 +37,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings; import android.util.IndentingPrintWriter; import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting; Loading @@ -60,7 +60,8 @@ import java.util.Objects; * and making calls async, leaving the (consequently more testable) {@link TimeZoneDetectorStrategy} * and making calls async, leaving the (consequently more testable) {@link TimeZoneDetectorStrategy} * implementation to deal with the logic around time zone detection. * implementation to deal with the logic around time zone detection. */ */ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub { public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub implements IBinder.DeathRecipient { private static final String TAG = "TimeZoneDetectorService"; private static final String TAG = "TimeZoneDetectorService"; Loading Loading @@ -104,9 +105,15 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub @NonNull @NonNull private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy; private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy; /** * This sparse array acts as a map from userId to listeners running as that userId. User scoped * as time zone detection configuration is partially user-specific, so different users can * get different configuration. */ @GuardedBy("mConfigurationListeners") @GuardedBy("mConfigurationListeners") @NonNull @NonNull private final ArrayList<ConfigListenerInfo> mConfigurationListeners = new ArrayList<>(); private final SparseArray<ArrayList<ITimeZoneConfigurationListener>> mConfigurationListeners = new SparseArray<>(); private static TimeZoneDetectorService create( private static TimeZoneDetectorService create( @NonNull Context context, @NonNull Handler handler, @NonNull Context context, @NonNull Handler handler, Loading Loading @@ -188,18 +195,23 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub Objects.requireNonNull(listener); Objects.requireNonNull(listener); int userId = UserHandle.getCallingUserId(); int userId = UserHandle.getCallingUserId(); ConfigListenerInfo listenerInfo = new ConfigListenerInfo(userId, listener); synchronized (mConfigurationListeners) { synchronized (mConfigurationListeners) { if (mConfigurationListeners.contains(listenerInfo)) { ArrayList<ITimeZoneConfigurationListener> listeners = mConfigurationListeners.get(userId); if (listeners != null && listeners.contains(listener)) { return; return; } } try { try { // Ensure the reference to the listener is removed if the client process dies. if (listeners == null) { listenerInfo.linkToDeath(); listeners = new ArrayList<>(1); mConfigurationListeners.put(userId, listeners); } // Ensure the reference to the listener will be removed if the client process dies. listener.asBinder().linkToDeath(this, 0 /* flags */); // Only add the listener if we can linkToDeath(). // Only add the listener if we can linkToDeath(). mConfigurationListeners.add(listenerInfo); listeners.add(listener); } catch (RemoteException e) { } catch (RemoteException e) { Slog.e(TAG, "Unable to linkToDeath() for listener=" + listener, e); Slog.e(TAG, "Unable to linkToDeath() for listener=" + listener, e); } } Loading @@ -213,22 +225,57 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub int userId = UserHandle.getCallingUserId(); int userId = UserHandle.getCallingUserId(); synchronized (mConfigurationListeners) { synchronized (mConfigurationListeners) { ConfigListenerInfo toRemove = new ConfigListenerInfo(userId, listener); boolean removedListener = false; Iterator<ConfigListenerInfo> listenerIterator = mConfigurationListeners.iterator(); ArrayList<ITimeZoneConfigurationListener> userListeners = while (listenerIterator.hasNext()) { mConfigurationListeners.get(userId); ConfigListenerInfo currentListenerInfo = listenerIterator.next(); if (userListeners.remove(listener)) { if (currentListenerInfo.equals(toRemove)) { listenerIterator.remove(); // Stop listening for the client process to die. // Stop listening for the client process to die. try { listener.asBinder().unlinkToDeath(this, 0 /* flags */); currentListenerInfo.unlinkToDeath(); removedListener = true; } catch (RemoteException e) { Slog.e(TAG, "Unable to unlinkToDeath() for listener=" + listener, e); } } if (!removedListener) { Slog.w(TAG, "Client asked to remove listenener=" + listener + ", but no listeners were removed." + " mConfigurationListeners=" + mConfigurationListeners); } } } } } } @Override public void binderDied() { // Should not be used as binderDied(IBinder who) is overridden. Slog.wtf(TAG, "binderDied() called unexpectedly."); } /** * Called when one of the ITimeZoneConfigurationListener processes dies before calling * {@link #removeConfigurationListener(ITimeZoneConfigurationListener)}. */ @Override public void binderDied(IBinder who) { synchronized (mConfigurationListeners) { boolean removedListener = false; final int userCount = mConfigurationListeners.size(); for (int i = 0; i < userCount; i++) { ArrayList<ITimeZoneConfigurationListener> userListeners = mConfigurationListeners.valueAt(i); Iterator<ITimeZoneConfigurationListener> userListenerIterator = userListeners.iterator(); while (userListenerIterator.hasNext()) { ITimeZoneConfigurationListener userListener = userListenerIterator.next(); if (userListener.asBinder().equals(who)) { userListenerIterator.remove(); removedListener = true; break; } } } if (!removedListener) { Slog.w(TAG, "Notified of binder death for who=" + who + ", but did not remove any listeners." + " mConfigurationListeners=" + mConfigurationListeners); } } } } void handleConfigurationChanged() { void handleConfigurationChanged() { Loading @@ -243,14 +290,24 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub // problem. // problem. synchronized (mConfigurationListeners) { synchronized (mConfigurationListeners) { for (ConfigListenerInfo listenerInfo : mConfigurationListeners) { final int userCount = mConfigurationListeners.size(); for (int userIndex = 0; userIndex < userCount; userIndex++) { int userId = mConfigurationListeners.keyAt(userIndex); TimeZoneConfiguration configuration = TimeZoneConfiguration configuration = mTimeZoneDetectorStrategy.getConfiguration(listenerInfo.getUserId()); mTimeZoneDetectorStrategy.getConfiguration(userId); ArrayList<ITimeZoneConfigurationListener> listeners = mConfigurationListeners.valueAt(userIndex); final int listenerCount = listeners.size(); for (int listenerIndex = 0; listenerIndex < listenerCount; listenerIndex++) { ITimeZoneConfigurationListener listener = listeners.get(listenerIndex); try { try { listenerInfo.getListener().onChange(configuration); listener.onChange(configuration); } catch (RemoteException e) { } catch (RemoteException e) { Slog.w(TAG, "Unable to notify listener=" Slog.w(TAG, "Unable to notify listener=" + listener + listenerInfo + " of updated configuration=" + configuration, e); + " for userId=" + userId + " of updated configuration=" + configuration, e); } } } } } } } Loading Loading @@ -338,66 +395,5 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub (new TimeZoneDetectorShellCommand(this)).exec( (new TimeZoneDetectorShellCommand(this)).exec( this, in, out, err, args, callback, resultReceiver); this, in, out, err, args, callback, resultReceiver); } } private class ConfigListenerInfo implements IBinder.DeathRecipient { private final @UserIdInt int mUserId; private final ITimeZoneConfigurationListener mListener; ConfigListenerInfo( @UserIdInt int userId, @NonNull ITimeZoneConfigurationListener listener) { this.mUserId = userId; this.mListener = Objects.requireNonNull(listener); } @UserIdInt int getUserId() { return mUserId; } ITimeZoneConfigurationListener getListener() { return mListener; } void linkToDeath() throws RemoteException { mListener.asBinder().linkToDeath(this, 0 /* flags */); } void unlinkToDeath() throws RemoteException { mListener.asBinder().unlinkToDeath(this, 0 /* flags */); } @Override public void binderDied() { synchronized (mConfigurationListeners) { Slog.i(TAG, "Configuration listener client died: " + this); mConfigurationListeners.remove(this); } } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ConfigListenerInfo that = (ConfigListenerInfo) o; return mUserId == that.mUserId && mListener.equals(that.mListener); } @Override public int hashCode() { return Objects.hash(mUserId, mListener); } @Override public String toString() { return "ConfigListenerInfo{" + "mUserId=" + mUserId + ", mListener=" + mListener + '}'; } } } }
services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java +1 −1 Original line number Original line Diff line number Diff line Loading @@ -170,7 +170,7 @@ public class TimeZoneDetectorServiceTest { ITimeZoneConfigurationListener mockListener = mock(ITimeZoneConfigurationListener.class); ITimeZoneConfigurationListener mockListener = mock(ITimeZoneConfigurationListener.class); try { try { mTimeZoneDetectorService.removeConfigurationListener(mockListener); mTimeZoneDetectorService.removeConfigurationListener(mockListener); fail(); fail("Expected a SecurityException"); } finally { } finally { verify(mMockContext).enforceCallingPermission( verify(mMockContext).enforceCallingPermission( eq(android.Manifest.permission.WRITE_SECURE_SETTINGS), eq(android.Manifest.permission.WRITE_SECURE_SETTINGS), Loading