1 /*
2  * Copyright (C) 2012 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.contacts.common.database;
18 
19 import android.content.AsyncQueryHandler;
20 import android.content.ContentResolver;
21 import android.database.Cursor;
22 import android.net.Uri;
23 import android.support.annotation.Nullable;
24 import android.support.annotation.VisibleForTesting;
25 import java.util.concurrent.atomic.AtomicInteger;
26 
27 /**
28  * An {@AsyncQueryHandler} that will never return a null cursor.
29  *
30  * <p>Instead, will return a {@link Cursor} with 0 records.
31  */
32 public abstract class NoNullCursorAsyncQueryHandler extends AsyncQueryHandler {
33   private static final AtomicInteger pendingQueryCount = new AtomicInteger();
34   @Nullable private static PendingQueryCountChangedListener pendingQueryCountChangedListener;
35 
NoNullCursorAsyncQueryHandler(ContentResolver cr)36   public NoNullCursorAsyncQueryHandler(ContentResolver cr) {
37     super(cr);
38   }
39 
40   @Override
startQuery( int token, Object cookie, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy)41   public void startQuery(
42       int token,
43       Object cookie,
44       Uri uri,
45       String[] projection,
46       String selection,
47       String[] selectionArgs,
48       String orderBy) {
49     pendingQueryCount.getAndIncrement();
50     if (pendingQueryCountChangedListener != null) {
51       pendingQueryCountChangedListener.onPendingQueryCountChanged();
52     }
53 
54     final CookieWithProjection projectionCookie = new CookieWithProjection(cookie, projection);
55     super.startQuery(token, projectionCookie, uri, projection, selection, selectionArgs, orderBy);
56   }
57 
58   @Override
onQueryComplete(int token, Object cookie, Cursor cursor)59   protected final void onQueryComplete(int token, Object cookie, Cursor cursor) {
60     CookieWithProjection projectionCookie = (CookieWithProjection) cookie;
61 
62     super.onQueryComplete(token, projectionCookie.originalCookie, cursor);
63 
64     if (cursor == null) {
65       cursor = new EmptyCursor(projectionCookie.projection);
66     }
67     onNotNullableQueryComplete(token, projectionCookie.originalCookie, cursor);
68 
69     pendingQueryCount.getAndDecrement();
70     if (pendingQueryCountChangedListener != null) {
71       pendingQueryCountChangedListener.onPendingQueryCountChanged();
72     }
73   }
74 
onNotNullableQueryComplete(int token, Object cookie, Cursor cursor)75   protected abstract void onNotNullableQueryComplete(int token, Object cookie, Cursor cursor);
76 
77   @VisibleForTesting(otherwise = VisibleForTesting.NONE)
setPendingQueryCountChangedListener( @ullable PendingQueryCountChangedListener listener)78   public static void setPendingQueryCountChangedListener(
79       @Nullable PendingQueryCountChangedListener listener) {
80     pendingQueryCountChangedListener = listener;
81   }
82 
83   @VisibleForTesting(otherwise = VisibleForTesting.NONE)
getPendingQueryCount()84   public static int getPendingQueryCount() {
85     return pendingQueryCount.get();
86   }
87 
88   /** Callback to listen for changes in the number of queries that have not completed. */
89   public interface PendingQueryCountChangedListener {
onPendingQueryCountChanged()90     void onPendingQueryCountChanged();
91   }
92 
93   /** Class to add projection to an existing cookie. */
94   private static class CookieWithProjection {
95 
96     public final Object originalCookie;
97     public final String[] projection;
98 
CookieWithProjection(Object cookie, String[] projection)99     public CookieWithProjection(Object cookie, String[] projection) {
100       this.originalCookie = cookie;
101       this.projection = projection;
102     }
103   }
104 }
105