1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.chrome.browser.usage_stats;
6 
7 import org.chromium.base.Function;
8 import org.chromium.base.Promise;
9 
10 import java.util.ArrayList;
11 import java.util.List;
12 
13 /**
14  * Class that tracks which sites are currently suspended.
15  */
16 public class SuspensionTracker {
17     private final UsageStatsBridge mBridge;
18     private final NotificationSuspender mNotificationSuspender;
19     private final Promise<List<String>> mRootPromise;
20     private Promise<Void> mWritePromise;
21 
SuspensionTracker(UsageStatsBridge bridge, NotificationSuspender notificationSuspender)22     public SuspensionTracker(UsageStatsBridge bridge, NotificationSuspender notificationSuspender) {
23         mBridge = bridge;
24         mNotificationSuspender = notificationSuspender;
25         mRootPromise = new Promise<>();
26         mBridge.getAllSuspensions((result) -> { mRootPromise.fulfill(result); });
27         mWritePromise = Promise.fulfilled(null);
28     }
29 
30     /**
31      * Sets the status of <c>fqdns</c> to match <c>suspended</c>.
32      * The returned promise will be fulfilled once persistence succeeds, and rejected if persistence
33      * fails.
34      */
setWebsitesSuspended(List<String> fqdns, boolean suspended)35     public Promise<Void> setWebsitesSuspended(List<String> fqdns, boolean suspended) {
36         Promise<Void> newWritePromise = new Promise<>();
37         mWritePromise.then((dummyResult) -> {
38             mRootPromise.then((result) -> {
39                 // We copy result so that the mutation isn't reflected in result until persistence
40                 // succeeds.
41                 List<String> resultCopy = new ArrayList<>(result);
42                 if (suspended) {
43                     UsageStatsMetricsReporter.reportMetricsEvent(
44                             UsageStatsMetricsEvent.SUSPEND_SITES);
45                     resultCopy.addAll(fqdns);
46                 } else {
47                     UsageStatsMetricsReporter.reportMetricsEvent(
48                             UsageStatsMetricsEvent.UNSUSPEND_SITES);
49                     resultCopy.removeAll(fqdns);
50                 }
51 
52                 mBridge.setSuspensions(
53                         resultCopy.toArray(new String[resultCopy.size()]), (didSucceed) -> {
54                             if (didSucceed) {
55                                 if (suspended) {
56                                     result.addAll(fqdns);
57                                 } else {
58                                     result.removeAll(fqdns);
59                                 }
60                                 mNotificationSuspender.setWebsitesSuspended(fqdns, suspended);
61                                 newWritePromise.fulfill(null);
62                             } else {
63                                 newWritePromise.reject();
64                             }
65                         });
66                 // We need to add a dummy exception handler so that Promise doesn't complain when we
67                 // call variants of then() that don't take a single callback. These variants set an
68                 // exception handler on the returned promise, so they expect there to be one on the
69                 // root promise.
70             }, (e) -> {});
71         });
72 
73         mWritePromise = newWritePromise;
74         return newWritePromise;
75     }
76 
getAllSuspendedWebsites()77     public Promise<List<String>> getAllSuspendedWebsites() {
78         return mRootPromise.then(
79                 (Function<List<String>, List<String>>) (result) -> { return result; });
80     }
81 
isWebsiteSuspended(String fqdn)82     public boolean isWebsiteSuspended(String fqdn) {
83         // We special case isWebsiteSuspended to return a value immediately because its only
84         // consumer(PageViewOsberver) only cares about immediate results.
85         if (mRootPromise != null && mRootPromise.isFulfilled()) {
86             return mRootPromise.getResult().contains(fqdn);
87         }
88 
89         return false;
90     }
91 }