@@ -24,6 +24,7 @@ package com.microsoft.identity.common.internal.activebrokerdiscovery
2424
2525import android.content.Context
2626import android.os.Bundle
27+ import androidx.annotation.VisibleForTesting
2728import com.microsoft.identity.common.exception.BrokerCommunicationException
2829import com.microsoft.identity.common.internal.broker.BrokerData
2930import com.microsoft.identity.common.internal.broker.BrokerValidator
@@ -206,6 +207,21 @@ class BrokerDiscoveryClient(private val brokerCandidates: Set<BrokerData>,
206207 }
207208 }
208209
210+ data class CachedBrokerData (
211+ val brokerData : BrokerData ?
212+ )
213+
214+ /* *
215+ * In-memory cache for the active broker data.
216+ * There are 3 possible states:
217+ * 1. null: the cache hasn't been initialized (needs to read from storage or query from broker first).
218+ * 2. CachedBrokerData with null brokerData: no active broker found.
219+ * 3. CachedBrokerData with non-null brokerData: found an active broker.
220+ **/
221+ @Volatile
222+ @VisibleForTesting
223+ var cachedData: CachedBrokerData ? = null
224+
209225 constructor (context: Context ,
210226 components: IPlatformComponents ,
211227 cache: IClientActiveBrokerCache ) : this (
@@ -273,9 +289,59 @@ class BrokerDiscoveryClient(private val brokerCandidates: Set<BrokerData>,
273289 }
274290 }
275291
292+ override fun getActiveBrokerWithInMemoryCache (telemetryCallback : IBrokerDiscoveryClientTelemetryCallback ? ): BrokerData ? {
293+ cachedData?.let {
294+ if (it.brokerData == null ) {
295+ return null
296+ }
297+ if (validateInMemoryCacheValue(it.brokerData, telemetryCallback)){
298+ return it.brokerData
299+ }
300+
301+ // Even if the value is not valid, we don't modify (invalidate) the class variable here since we're not in the lock.
302+ // The variable will be updated in the block below.
303+ }
304+
305+ return runBlocking {
306+ val timeStartAcquiringLock = System .nanoTime()
307+ classLevelLock.withLock {
308+ telemetryCallback?.onLockAcquired(System .nanoTime() - timeStartAcquiringLock)
309+
310+ // just in case the value is already populated while waiting for the lock.
311+ cachedData?.let {
312+ if (it.brokerData == null ) {
313+ return @runBlocking null
314+ }
315+ if (validateInMemoryCacheValue(it.brokerData, telemetryCallback)){
316+ return @runBlocking it.brokerData
317+ }
318+ }
319+
320+ val brokerData = getActiveBrokerAsync(shouldSkipCache = false , telemetryCallback)
321+ cachedData = CachedBrokerData (brokerData)
322+ return @runBlocking brokerData
323+ }
324+ }
325+ }
326+
327+ /* *
328+ * Make sure the [BrokerData] that stays in the memory cache (if any) is valid.
329+ **/
330+ private fun validateInMemoryCacheValue (
331+ data : BrokerData ,
332+ telemetryCallback : IBrokerDiscoveryClientTelemetryCallback ? ,
333+ ): Boolean {
334+ val timeStartIsValidBroker = System .nanoTime()
335+ val isValidBroker = isValidBroker(data)
336+ telemetryCallback?.onFinishCheckingIfValidBroker(System .nanoTime() - timeStartIsValidBroker)
337+ return isValidBroker
338+ }
339+
276340 override fun getActiveBroker (shouldSkipCache : Boolean ): BrokerData ? {
277341 return runBlocking {
278- return @runBlocking getActiveBrokerAsync(shouldSkipCache, null )
342+ classLevelLock.withLock {
343+ return @runBlocking getActiveBrokerAsync(shouldSkipCache, null )
344+ }
279345 }
280346 }
281347
@@ -284,102 +350,88 @@ class BrokerDiscoveryClient(private val brokerCandidates: Set<BrokerData>,
284350 telemetryCallback : IBrokerDiscoveryClientTelemetryCallback
285351 ): BrokerData ? {
286352 return runBlocking {
287- return @runBlocking getActiveBrokerAsync(shouldSkipCache, telemetryCallback)
353+ val timeStartAcquiringLock = System .nanoTime()
354+ classLevelLock.withLock {
355+ telemetryCallback.onLockAcquired(System .nanoTime() - timeStartAcquiringLock)
356+ return @runBlocking getActiveBrokerAsync(shouldSkipCache, telemetryCallback)
357+ }
288358 }
289359 }
290360
291361 private suspend fun getActiveBrokerAsync (shouldSkipCache : Boolean ,
292362 telemetryCallback : IBrokerDiscoveryClientTelemetryCallback ? ): BrokerData ? {
293363 val methodTag = " $TAG :getActiveBrokerAsync"
294-
295- val timeStartAcquiringLock = System .nanoTime()
296- classLevelLock.withLock {
297- telemetryCallback?.onLockAcquired(System .nanoTime() - timeStartAcquiringLock)
298- if (! shouldSkipCache) {
299- if (cache.shouldUseAccountManager()) {
300- telemetryCallback?.onUseAccountManager()
301- return getActiveBrokerFromAccountManager()
364+ if (! shouldSkipCache) {
365+ if (cache.shouldUseAccountManager()) {
366+ telemetryCallback?.onUseAccountManager()
367+ return getActiveBrokerFromAccountManager()
368+ }
369+ val timeStartReadingFromCache = System .nanoTime()
370+ cache.getCachedActiveBroker()?.let {
371+ telemetryCallback?.onReadFromCache(System .nanoTime() - timeStartReadingFromCache)
372+
373+ val timeStartIsPackageInstalled = System .nanoTime()
374+ val isPackageInstalled = isPackageInstalled(it)
375+ telemetryCallback?.onFinishCheckingIfPackageIsInstalled(System .nanoTime() - timeStartIsPackageInstalled)
376+ if (! isPackageInstalled) {
377+ Logger .info(
378+ methodTag,
379+ " There is a cached broker: $it , but the app is no longer installed."
380+ )
381+ cache.clearCachedActiveBroker()
382+ return @let
302383 }
303- val timeStartReadingFromCache = System .nanoTime()
304- cache.getCachedActiveBroker()?.let {
305- telemetryCallback?.onReadFromCache(System .nanoTime() - timeStartReadingFromCache)
306-
307- val timeStartIsPackageInstalled = System .nanoTime()
308- val isPackageInstalled = isPackageInstalled(it)
309- telemetryCallback?.onFinishCheckingIfPackageIsInstalled(System .nanoTime() - timeStartIsPackageInstalled)
310- if (! isPackageInstalled) {
311- Logger .info(
312- methodTag,
313- " There is a cached broker: $it , but the app is no longer installed."
314- )
315- cache.clearCachedActiveBroker()
316- return @let
317- }
318384
319- val timeStartIsValidBroker = System .nanoTime()
320- val isValidBroker = isValidBroker(it)
321- telemetryCallback?.onFinishCheckingIfValidBroker(System .nanoTime() - timeStartIsValidBroker)
322- if (! isValidBroker) {
323- Logger .info(
324- methodTag,
325- " Clearing cache as the installed app does not have a matching signature hash."
326- )
327- cache.clearCachedActiveBroker()
328- return @let
329- }
330-
331- val timeStartIsSupportedByTargetedBroker = System .nanoTime()
332- val isSupportedByTargetedBroker =
333- ipcStrategy.isSupportedByTargetedBroker(it.packageName)
334- telemetryCallback?.onFinishCheckingIfSupportedByTargetedBroker(System .nanoTime() - timeStartIsSupportedByTargetedBroker)
335- if (! isSupportedByTargetedBroker){
336- Logger .info(
337- methodTag,
338- " Clearing cache as the installed app does not provide any IPC mechanism to communicate to. (e.g. the broker code isn't shipped with this apk)"
339- )
340- cache.clearCachedActiveBroker()
341- return @let
342- }
343-
344- Logger .info(methodTag, " Returning cached broker: $it " )
345- return it
385+ val timeStartIsValidBroker = System .nanoTime()
386+ val isValidBroker = isValidBroker(it)
387+ telemetryCallback?.onFinishCheckingIfValidBroker(System .nanoTime() - timeStartIsValidBroker)
388+ if (! isValidBroker) {
389+ Logger .info(
390+ methodTag,
391+ " Clearing cache as the installed app does not have a matching signature hash."
392+ )
393+ cache.clearCachedActiveBroker()
394+ return @let
346395 }
347- }
348396
349- val timeStartQueryFromBroker = System .nanoTime()
350- val brokerData = queryFromBroker(
351- brokerCandidates = brokerCandidates,
352- ipcStrategy = ipcStrategy,
353- isPackageInstalled = isPackageInstalled,
354- isValidBroker = isValidBroker
355- )
356- telemetryCallback?.onFinishQueryingResultFromBroker(System .nanoTime() - timeStartQueryFromBroker)
357-
358- if (brokerData != null ) {
359- cache.setCachedActiveBroker(brokerData)
360- return brokerData
397+ Logger .info(methodTag, " Returning cached broker: $it " )
398+ return it
361399 }
400+ }
362401
363- Logger .info(
364- methodTag,
365- " Will skip broker discovery via IPC and fall back to AccountManager " +
366- " for the next 60 minutes."
367- )
368- cache.clearCachedActiveBroker()
369- cache.setShouldUseAccountManagerForTheNextMilliseconds(
370- TimeUnit .MINUTES .toMillis(
371- 60
372- )
373- )
402+ val timeStartQueryFromBroker = System .nanoTime()
403+ val brokerData = queryFromBroker(
404+ brokerCandidates = brokerCandidates,
405+ ipcStrategy = ipcStrategy,
406+ isPackageInstalled = isPackageInstalled,
407+ isValidBroker = isValidBroker
408+ )
409+ telemetryCallback?.onFinishQueryingResultFromBroker(System .nanoTime() - timeStartQueryFromBroker)
410+
411+ if (brokerData != null ) {
412+ cache.setCachedActiveBroker(brokerData)
413+ return brokerData
414+ }
374415
375- telemetryCallback?.onUseAccountManager()
376- val accountManagerResult = getActiveBrokerFromAccountManager()
377- Logger .info(
378- methodTag, " Tried getting active broker from account manager, " +
379- " get ${accountManagerResult?.packageName} ."
416+ Logger .info(
417+ methodTag,
418+ " Will skip broker discovery via IPC and fall back to AccountManager " +
419+ " for the next 60 minutes."
420+ )
421+ cache.clearCachedActiveBroker()
422+ cache.setShouldUseAccountManagerForTheNextMilliseconds(
423+ TimeUnit .MINUTES .toMillis(
424+ 60
380425 )
426+ )
381427
382- return accountManagerResult
383- }
428+ telemetryCallback?.onUseAccountManager()
429+ val accountManagerResult = getActiveBrokerFromAccountManager()
430+ Logger .info(
431+ methodTag, " Tried getting active broker from account manager, " +
432+ " get ${accountManagerResult?.packageName} ."
433+ )
434+
435+ return accountManagerResult
384436 }
385437}
0 commit comments