1 /*
2  * Copyright (C) 2018 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 package com.android.internal.util;
17 
18 import android.annotation.NonNull;
19 import android.annotation.Nullable;
20 import android.os.Bundle;
21 import android.os.Parcelable;
22 
23 import com.android.internal.os.IResultReceiver;
24 
25 import java.util.ArrayList;
26 import java.util.concurrent.CountDownLatch;
27 import java.util.concurrent.TimeUnit;
28 
29 /**
30  * A {@code IResultReceiver} implementation that can be used to make "sync" Binder calls by blocking
31  * until it receives a result
32  *
33  * @hide
34  */
35 public final class SyncResultReceiver extends IResultReceiver.Stub {
36 
37     private static final String EXTRA = "EXTRA";
38 
39     private final CountDownLatch mLatch  = new CountDownLatch(1);
40     private final int mTimeoutMs;
41     private int mResult;
42     private Bundle mBundle;
43 
44     /**
45      * Default constructor.
46      *
47      * @param timeoutMs how long to block waiting for {@link IResultReceiver} callbacks.
48      */
SyncResultReceiver(int timeoutMs)49     public SyncResultReceiver(int timeoutMs) {
50         mTimeoutMs = timeoutMs;
51     }
52 
waitResult()53     private void waitResult() throws TimeoutException {
54         try {
55             if (!mLatch.await(mTimeoutMs, TimeUnit.MILLISECONDS)) {
56                 throw new TimeoutException("Not called in " + mTimeoutMs + "ms");
57             }
58         } catch (InterruptedException e) {
59             Thread.currentThread().interrupt();
60             throw new TimeoutException("Interrupted");
61         }
62     }
63 
64     /**
65      * Gets the result from an operation that returns an {@code int}.
66      */
getIntResult()67     public int getIntResult() throws TimeoutException {
68         waitResult();
69         return mResult;
70     }
71 
72     /**
73      * Gets the result from an operation that returns an {@code String}.
74      */
75     @Nullable
getStringResult()76     public String getStringResult() throws TimeoutException {
77         waitResult();
78         return mBundle == null ? null : mBundle.getString(EXTRA);
79     }
80 
81     /**
82      * Gets the result from an operation that returns a {@code String[]}.
83      */
84     @Nullable
getStringArrayResult()85     public String[] getStringArrayResult() throws TimeoutException {
86         waitResult();
87         return mBundle == null ? null : mBundle.getStringArray(EXTRA);
88     }
89 
90     /**
91      * Gets the result from an operation that returns a {@code Parcelable}.
92      */
93     @Nullable
getParcelableResult()94     public <P extends Parcelable> P getParcelableResult() throws TimeoutException {
95         waitResult();
96         return mBundle == null ? null : mBundle.getParcelable(EXTRA);
97     }
98 
99     /**
100      * Gets the result from an operation that returns a {@code Parcelable} list.
101      */
102     @Nullable
getParcelableListResult()103     public <P extends Parcelable> ArrayList<P> getParcelableListResult() throws TimeoutException {
104         waitResult();
105         return mBundle == null ? null : mBundle.getParcelableArrayList(EXTRA);
106     }
107 
108     /**
109      * Gets the optional result from an operation that returns an extra {@code int} (besides the
110      * result code).
111      *
112      * @return value set in the bundle, or {@code defaultValue} when not set.
113      */
getOptionalExtraIntResult(int defaultValue)114     public int getOptionalExtraIntResult(int defaultValue) throws TimeoutException {
115         waitResult();
116         if (mBundle == null || !mBundle.containsKey(EXTRA)) return defaultValue;
117 
118         return mBundle.getInt(EXTRA);
119     }
120 
121     @Override
send(int resultCode, Bundle resultData)122     public void send(int resultCode, Bundle resultData) {
123         mResult = resultCode;
124         mBundle = resultData;
125         mLatch.countDown();
126     }
127 
128     /**
129      * Creates a bundle for a {@code String} value so it can be retrieved by
130      * {@link #getStringResult()}.
131      */
132     @NonNull
bundleFor(@ullable String value)133     public static Bundle bundleFor(@Nullable String value) {
134         final Bundle bundle = new Bundle();
135         bundle.putString(EXTRA, value);
136         return bundle;
137     }
138 
139     /**
140      * Creates a bundle for a {@code String[]} value so it can be retrieved by
141      * {@link #getStringArrayResult()}.
142      */
143     @NonNull
bundleFor(@ullable String[] value)144     public static Bundle bundleFor(@Nullable String[] value) {
145         final Bundle bundle = new Bundle();
146         bundle.putStringArray(EXTRA, value);
147         return bundle;
148     }
149 
150     /**
151      * Creates a bundle for a {@code Parcelable} value so it can be retrieved by
152      * {@link #getParcelableResult()}.
153      */
154     @NonNull
bundleFor(@ullable Parcelable value)155     public static Bundle bundleFor(@Nullable Parcelable value) {
156         final Bundle bundle = new Bundle();
157         bundle.putParcelable(EXTRA, value);
158         return bundle;
159     }
160 
161     /**
162      * Creates a bundle for a {@code Parcelable} list so it can be retrieved by
163      * {@link #getParcelableResult()}.
164      */
165     @NonNull
bundleFor(@ullable ArrayList<? extends Parcelable> value)166     public static Bundle bundleFor(@Nullable ArrayList<? extends Parcelable> value) {
167         final Bundle bundle = new Bundle();
168         bundle.putParcelableArrayList(EXTRA, value);
169         return bundle;
170     }
171 
172     /**
173      * Creates a bundle for an {@code int} value so it can be retrieved by
174      * {@link #getParcelableResult()} - typically used to return an extra {@code int} (as the 1st
175      * is returned as the result code).
176      */
177     @NonNull
bundleFor(int value)178     public static Bundle bundleFor(int value) {
179         final Bundle bundle = new Bundle();
180         bundle.putInt(EXTRA, value);
181         return bundle;
182     }
183 
184     /** @hide */
185     public static final class TimeoutException extends RuntimeException {
TimeoutException(String msg)186         private TimeoutException(String msg) {
187             super(msg);
188         }
189     }
190 }
191