• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.documentsui.archives;
18 
19 import com.android.internal.annotations.GuardedBy;
20 
21 import android.content.Context;
22 import android.net.Uri;
23 import android.util.Log;
24 
25 import java.io.File;
26 import java.io.FileNotFoundException;
27 import java.io.IOException;
28 import java.util.concurrent.ExecutorService;
29 import java.util.concurrent.Executors;
30 import java.util.concurrent.locks.Lock;
31 
32 /**
33  * Loads an instance of Archive lazily.
34  */
35 public class Loader {
36     private static final String TAG = "Loader";
37 
38     public static final int STATUS_OPENING = 0;
39     public static final int STATUS_OPENED = 1;
40     public static final int STATUS_FAILED = 2;
41     public static final int STATUS_CLOSING = 3;
42     public static final int STATUS_CLOSED = 4;
43 
44     private final Context mContext;
45     private final Uri mArchiveUri;
46     private final int mAccessMode;
47     private final Uri mNotificationUri;
48     private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
49     private final Object mLock = new Object();
50     @GuardedBy("mLock")
51     private int mStatus = STATUS_OPENING;
52     @GuardedBy("mLock")
53     private int mRefCount = 0;
54     private Archive mArchive = null;
55 
Loader(Context context, Uri archiveUri, int accessMode, Uri notificationUri)56     Loader(Context context, Uri archiveUri, int accessMode, Uri notificationUri) {
57         this.mContext = context;
58         this.mArchiveUri = archiveUri;
59         this.mAccessMode = accessMode;
60         this.mNotificationUri = notificationUri;
61 
62         // Start loading the archive immediately in the background.
63         mExecutor.submit(this::get);
64     }
65 
get()66     synchronized Archive get() {
67         synchronized (mLock) {
68             if (mStatus == STATUS_OPENED) {
69                 return mArchive;
70             }
71         }
72 
73         synchronized (mLock) {
74             if (mStatus != STATUS_OPENING) {
75                 throw new IllegalStateException(
76                         "Trying to perform an operation on an archive which is invalidated.");
77             }
78         }
79 
80         try {
81             if (ReadableArchive.supportsAccessMode(mAccessMode)) {
82                 mArchive = ReadableArchive.createForParcelFileDescriptor(
83                         mContext,
84                         mContext.getContentResolver().openFileDescriptor(
85                                 mArchiveUri, "r", null /* signal */),
86                         mArchiveUri, mAccessMode, mNotificationUri);
87             } else if (WriteableArchive.supportsAccessMode(mAccessMode)) {
88                 mArchive = WriteableArchive.createForParcelFileDescriptor(
89                         mContext,
90                         mContext.getContentResolver().openFileDescriptor(
91                                 mArchiveUri, "w", null /* signal */),
92                         mArchiveUri, mAccessMode, mNotificationUri);
93             } else {
94                 throw new IllegalStateException("Access mode not supported.");
95             }
96             synchronized (mLock) {
97                 if (mRefCount == 0) {
98                     mArchive.close();
99                     mStatus = STATUS_CLOSED;
100                 } else {
101                     mStatus = STATUS_OPENED;
102                 }
103             }
104         } catch (IOException | RuntimeException e) {
105             Log.e(TAG, "Failed to open the archive.", e);
106             synchronized (mLock) {
107                 mStatus = STATUS_FAILED;
108             }
109             throw new IllegalStateException("Failed to open the archive.", e);
110         } finally {
111             synchronized (mLock) {
112                 // Only notify when there might be someone listening.
113                 if (mRefCount > 0) {
114                     // Notify observers that the root directory is loaded (or failed)
115                     // so clients reload it.
116                     mContext.getContentResolver().notifyChange(
117                             ArchivesProvider.buildUriForArchive(mArchiveUri, mAccessMode),
118                             null /* observer */, false /* syncToNetwork */);
119                 }
120             }
121         }
122 
123         return mArchive;
124     }
125 
getStatus()126     int getStatus() {
127         synchronized (mLock) {
128             return mStatus;
129         }
130     }
131 
acquire()132     void acquire() {
133         synchronized (mLock) {
134             mRefCount++;
135         }
136     }
137 
release()138     void release() {
139         synchronized (mLock) {
140             mRefCount--;
141             if (mRefCount == 0) {
142                 assert(mStatus == STATUS_OPENING
143                         || mStatus == STATUS_OPENED
144                         || mStatus == STATUS_FAILED);
145 
146                 switch (mStatus) {
147                     case STATUS_OPENED:
148                         try {
149                             mArchive.close();
150                             mStatus = STATUS_CLOSED;
151                         } catch (IOException e) {
152                             Log.e(TAG, "Failed to close the archive on release.", e);
153                         }
154                         break;
155                     case STATUS_FAILED:
156                         mStatus = STATUS_CLOSED;
157                         break;
158                     case STATUS_OPENING:
159                         mStatus = STATUS_CLOSING;
160                         // ::get() will close the archive once opened.
161                         break;
162                 }
163             }
164         }
165     }
166 }
167