├── settings.gradle
├── screenshots
├── main.png
└── icon-web.png
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── Application
├── src
│ └── main
│ │ ├── res
│ │ ├── drawable-hdpi
│ │ │ ├── tile.9.png
│ │ │ └── ic_launcher.png
│ │ ├── drawable-mdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_action_refresh.png
│ │ ├── drawable-xxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── layout
│ │ │ ├── activity_entry_list.xml
│ │ │ └── actionbar_indeterminate_progress.xml
│ │ ├── values-v21
│ │ │ ├── base-colors.xml
│ │ │ └── base-template-styles.xml
│ │ ├── values-v11
│ │ │ └── template-styles.xml
│ │ ├── values
│ │ │ ├── dimen.xml
│ │ │ ├── strings.xml
│ │ │ ├── template-dimens.xml
│ │ │ ├── base-strings.xml
│ │ │ └── template-styles.xml
│ │ ├── values-sw600dp
│ │ │ ├── template-dimens.xml
│ │ │ └── template-styles.xml
│ │ ├── menu
│ │ │ └── main.xml
│ │ └── xml
│ │ │ ├── authenticator.xml
│ │ │ └── syncadapter.xml
│ │ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── android
│ │ │ ├── basicsyncadapter
│ │ │ ├── EntryListActivity.java
│ │ │ ├── SyncService.java
│ │ │ ├── provider
│ │ │ │ ├── FeedContract.java
│ │ │ │ └── FeedProvider.java
│ │ │ ├── SyncUtils.java
│ │ │ ├── net
│ │ │ │ └── FeedParser.java
│ │ │ ├── SyncAdapter.java
│ │ │ └── EntryListFragment.java
│ │ │ └── common
│ │ │ ├── logger
│ │ │ ├── LogNode.java
│ │ │ ├── MessageOnlyLogFilter.java
│ │ │ ├── LogWrapper.java
│ │ │ ├── LogFragment.java
│ │ │ ├── LogView.java
│ │ │ └── Log.java
│ │ │ ├── accounts
│ │ │ └── GenericAccountService.java
│ │ │ └── db
│ │ │ └── SelectionBuilder.java
│ │ └── AndroidManifest.xml
├── build.gradle
└── tests
│ ├── AndroidManifest.xml
│ └── src
│ └── com
│ └── example
│ └── android
│ └── basicsyncadapter
│ └── tests
│ └── SampleTests.java
├── README.md
├── packaging.yaml
├── .google
└── packaging.yaml
├── CONTRIBUTING.md
├── CONTRIB.md
├── gradlew.bat
├── gradlew
└── LICENSE
/settings.gradle:
--------------------------------------------------------------------------------
1 | include 'Application'
2 |
--------------------------------------------------------------------------------
/screenshots/main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/android-BasicSyncAdapter/master/screenshots/main.png
--------------------------------------------------------------------------------
/screenshots/icon-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/android-BasicSyncAdapter/master/screenshots/icon-web.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/android-BasicSyncAdapter/master/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/Application/src/main/res/drawable-hdpi/tile.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/android-BasicSyncAdapter/master/Application/src/main/res/drawable-hdpi/tile.9.png
--------------------------------------------------------------------------------
/Application/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/android-BasicSyncAdapter/master/Application/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Application/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/android-BasicSyncAdapter/master/Application/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Application/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/android-BasicSyncAdapter/master/Application/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Application/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/android-BasicSyncAdapter/master/Application/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Application/src/main/res/drawable-xhdpi/ic_action_refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/android-BasicSyncAdapter/master/Application/src/main/res/drawable-xhdpi/ic_action_refresh.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 10 15:27:10 PDT 2013
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | Android BasicSyncAdapter Sample
3 | ===============================
4 |
5 | This sample has been deprecated/archived meaning it's read-only and it's no longer actively maintained (more details on archiving can be found [here][1]).
6 |
7 | For other related samples, check out the new [github.com/android/background-tasks-samples][2] repo. Thank you!
8 |
9 | [1]: https://help.github.com/en/articles/about-archiving-repositories
10 | [2]: https://github.com/android/background-tasks-samples
11 |
--------------------------------------------------------------------------------
/Application/src/main/java/com/example/android/basicsyncadapter/EntryListActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.android.basicsyncadapter;
2 |
3 | import android.os.Bundle;
4 | import android.support.v4.app.FragmentActivity;
5 |
6 | /**
7 | * Activity for holding EntryListFragment.
8 | */
9 | public class EntryListActivity extends FragmentActivity {
10 |
11 | @Override
12 | protected void onCreate(Bundle savedInstanceState) {
13 | super.onCreate(savedInstanceState);
14 | setContentView(R.layout.activity_entry_list);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packaging.yaml:
--------------------------------------------------------------------------------
1 | # GOOGLE SAMPLE PACKAGING DATA
2 | #
3 | # This file is used by Google as part of our samples packaging process.
4 | # End users may safely ignore this file. It has no relevance to other systems.
5 | ---
6 |
7 | status: PUBLISHED
8 | technologies: [Android]
9 | categories: [Connectivity]
10 | languages: [Java]
11 | solutions: [Mobile]
12 | github: googlesamples/android-BasicSyncAdapter
13 | level: BEGINNER
14 | icon: BasicSyncAdapterSample/src/main/res/drawable-xxhdpi/ic_launcher.png
15 | license: apache2-android
16 |
--------------------------------------------------------------------------------
/Application/src/main/res/layout/activity_entry_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
This service is invoked in response to Intents with action android.content.SyncAdapter, and 27 | * returns a Binder connection to SyncAdapter. 28 | * 29 | *
For performance, only one sync adapter will be initialized within this application's context. 30 | * 31 | *
Note: The SyncService itself is not notified when a new sync occurs. It's role is to 32 | * manage the lifecycle of our {@link SyncAdapter} and provide a handle to said SyncAdapter to the 33 | * OS on request. 34 | */ 35 | public class SyncService extends Service { 36 | private static final String TAG = "SyncService"; 37 | 38 | private static final Object sSyncAdapterLock = new Object(); 39 | private static SyncAdapter sSyncAdapter = null; 40 | 41 | /** 42 | * Thread-safe constructor, creates static {@link SyncAdapter} instance. 43 | */ 44 | @Override 45 | public void onCreate() { 46 | super.onCreate(); 47 | Log.i(TAG, "Service created"); 48 | synchronized (sSyncAdapterLock) { 49 | if (sSyncAdapter == null) { 50 | sSyncAdapter = new SyncAdapter(getApplicationContext(), true); 51 | } 52 | } 53 | } 54 | 55 | @Override 56 | /** 57 | * Logging-only destructor. 58 | */ 59 | public void onDestroy() { 60 | super.onDestroy(); 61 | Log.i(TAG, "Service destroyed"); 62 | } 63 | 64 | /** 65 | * Return Binder handle for IPC communication with {@link SyncAdapter}. 66 | * 67 | *
New sync requests will be sent directly to the SyncAdapter using this channel.
68 | *
69 | * @param intent Calling intent
70 | * @return Binder handle for {@link SyncAdapter}
71 | */
72 | @Override
73 | public IBinder onBind(Intent intent) {
74 | return sSyncAdapter.getSyncAdapterBinder();
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Application/src/main/java/com/example/android/common/logger/LogWrapper.java:
--------------------------------------------------------------------------------
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 | package com.example.android.common.logger;
17 |
18 | import android.util.Log;
19 |
20 | /**
21 | * Helper class which wraps Android's native Log utility in the Logger interface. This way
22 | * normal DDMS output can be one of the many targets receiving and outputting logs simultaneously.
23 | */
24 | public class LogWrapper implements LogNode {
25 |
26 | // For piping: The next node to receive Log data after this one has done its work.
27 | private LogNode mNext;
28 |
29 | /**
30 | * Returns the next LogNode in the linked list.
31 | */
32 | public LogNode getNext() {
33 | return mNext;
34 | }
35 |
36 | /**
37 | * Sets the LogNode data will be sent to..
38 | */
39 | public void setNext(LogNode node) {
40 | mNext = node;
41 | }
42 |
43 | /**
44 | * Prints data out to the console using Android's native log mechanism.
45 | * @param priority Log level of the data being logged. Verbose, Error, etc.
46 | * @param tag Tag for for the log data. Can be used to organize log statements.
47 | * @param msg The actual message to be logged. The actual message to be logged.
48 | * @param tr If an exception was thrown, this can be sent along for the logging facilities
49 | * to extract and print useful information.
50 | */
51 | @Override
52 | public void println(int priority, String tag, String msg, Throwable tr) {
53 | // There actually are log methods that don't take a msg parameter. For now,
54 | // if that's the case, just convert null to the empty string and move on.
55 | String useMsg = msg;
56 | if (useMsg == null) {
57 | useMsg = "";
58 | }
59 |
60 | // If an exeption was provided, convert that exception to a usable string and attach
61 | // it to the end of the msg method.
62 | if (tr != null) {
63 | msg += "\n" + Log.getStackTraceString(tr);
64 | }
65 |
66 | // This is functionally identical to Log.x(tag, useMsg);
67 | // For instance, if priority were Log.VERBOSE, this would be the same as Log.v(tag, useMsg)
68 | Log.println(priority, tag, useMsg);
69 |
70 | // If this isn't the last node in the chain, move things along.
71 | if (mNext != null) {
72 | mNext.println(priority, tag, msg, tr);
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Application/src/main/java/com/example/android/basicsyncadapter/provider/FeedContract.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 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.example.android.basicsyncadapter.provider;
18 |
19 | import android.content.ContentResolver;
20 | import android.net.Uri;
21 | import android.provider.BaseColumns;
22 |
23 | /**
24 | * Field and table name constants for
25 | * {@link com.example.android.basicsyncadapter.provider.FeedProvider}.
26 | */
27 | public class FeedContract {
28 | private FeedContract() {
29 | }
30 |
31 | /**
32 | * Content provider authority.
33 | */
34 | public static final String CONTENT_AUTHORITY = "com.example.android.basicsyncadapter";
35 |
36 | /**
37 | * Base URI. (content://com.example.android.basicsyncadapter)
38 | */
39 | public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
40 |
41 | /**
42 | * Path component for "entry"-type resources..
43 | */
44 | private static final String PATH_ENTRIES = "entries";
45 |
46 | /**
47 | * Columns supported by "entries" records.
48 | */
49 | public static class Entry implements BaseColumns {
50 | /**
51 | * MIME type for lists of entries.
52 | */
53 | public static final String CONTENT_TYPE =
54 | ContentResolver.CURSOR_DIR_BASE_TYPE + "/vnd.basicsyncadapter.entries";
55 | /**
56 | * MIME type for individual entries.
57 | */
58 | public static final String CONTENT_ITEM_TYPE =
59 | ContentResolver.CURSOR_ITEM_BASE_TYPE + "/vnd.basicsyncadapter.entry";
60 |
61 | /**
62 | * Fully qualified URI for "entry" resources.
63 | */
64 | public static final Uri CONTENT_URI =
65 | BASE_CONTENT_URI.buildUpon().appendPath(PATH_ENTRIES).build();
66 |
67 | /**
68 | * Table name where records are stored for "entry" resources.
69 | */
70 | public static final String TABLE_NAME = "entry";
71 | /**
72 | * Atom ID. (Note: Not to be confused with the database primary key, which is _ID.
73 | */
74 | public static final String COLUMN_NAME_ENTRY_ID = "entry_id";
75 | /**
76 | * Article title
77 | */
78 | public static final String COLUMN_NAME_TITLE = "title";
79 | /**
80 | * Article hyperlink. Corresponds to the rel="alternate" link in the
81 | * Atom spec.
82 | */
83 | public static final String COLUMN_NAME_LINK = "link";
84 | /**
85 | * Date article was published.
86 | */
87 | public static final String COLUMN_NAME_PUBLISHED = "published";
88 | }
89 | }
--------------------------------------------------------------------------------
/Application/src/main/java/com/example/android/common/logger/LogFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 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 | * Copyright 2013 The Android Open Source Project
18 | *
19 | * Licensed under the Apache License, Version 2.0 (the "License");
20 | * you may not use this file except in compliance with the License.
21 | * You may obtain a copy of the License at
22 | *
23 | * http://www.apache.org/licenses/LICENSE-2.0
24 | *
25 | * Unless required by applicable law or agreed to in writing, software
26 | * distributed under the License is distributed on an "AS IS" BASIS,
27 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
28 | * See the License for the specific language governing permissions and
29 | * limitations under the License.
30 | */
31 |
32 | package com.example.android.common.logger;
33 |
34 | import android.graphics.Typeface;
35 | import android.os.Bundle;
36 | import android.support.v4.app.Fragment;
37 | import android.text.Editable;
38 | import android.text.TextWatcher;
39 | import android.view.Gravity;
40 | import android.view.LayoutInflater;
41 | import android.view.View;
42 | import android.view.ViewGroup;
43 | import android.widget.ScrollView;
44 |
45 | /**
46 | * Simple fraggment which contains a LogView and uses is to output log data it receives
47 | * through the LogNode interface.
48 | */
49 | public class LogFragment extends Fragment {
50 |
51 | private LogView mLogView;
52 | private ScrollView mScrollView;
53 |
54 | public LogFragment() {}
55 |
56 | public View inflateViews() {
57 | mScrollView = new ScrollView(getActivity());
58 | ViewGroup.LayoutParams scrollParams = new ViewGroup.LayoutParams(
59 | ViewGroup.LayoutParams.MATCH_PARENT,
60 | ViewGroup.LayoutParams.MATCH_PARENT);
61 | mScrollView.setLayoutParams(scrollParams);
62 |
63 | mLogView = new LogView(getActivity());
64 | ViewGroup.LayoutParams logParams = new ViewGroup.LayoutParams(scrollParams);
65 | logParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
66 | mLogView.setLayoutParams(logParams);
67 | mLogView.setClickable(true);
68 | mLogView.setFocusable(true);
69 | mLogView.setTypeface(Typeface.MONOSPACE);
70 |
71 | // Want to set padding as 16 dips, setPadding takes pixels. Hooray math!
72 | int paddingDips = 16;
73 | double scale = getResources().getDisplayMetrics().density;
74 | int paddingPixels = (int) ((paddingDips * (scale)) + .5);
75 | mLogView.setPadding(paddingPixels, paddingPixels, paddingPixels, paddingPixels);
76 | mLogView.setCompoundDrawablePadding(paddingPixels);
77 |
78 | mLogView.setGravity(Gravity.BOTTOM);
79 | mLogView.setTextAppearance(getActivity(), android.R.style.TextAppearance_Holo_Medium);
80 |
81 | mScrollView.addView(mLogView);
82 | return mScrollView;
83 | }
84 |
85 | @Override
86 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
87 | Bundle savedInstanceState) {
88 |
89 | View result = inflateViews();
90 |
91 | mLogView.addTextChangedListener(new TextWatcher() {
92 | @Override
93 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
94 |
95 | @Override
96 | public void onTextChanged(CharSequence s, int start, int before, int count) {}
97 |
98 | @Override
99 | public void afterTextChanged(Editable s) {
100 | mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
101 | }
102 | });
103 | return result;
104 | }
105 |
106 | public LogView getLogView() {
107 | return mLogView;
108 | }
109 | }
--------------------------------------------------------------------------------
/Application/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
This should only be used when we need to preempt the normal sync schedule. Typically, this 82 | * means the user has pressed the "refresh" button. 83 | * 84 | * Note that SYNC_EXTRAS_MANUAL will cause an immediate sync, without any optimization to 85 | * preserve battery life. If you know new data is available (perhaps via a GCM notification), 86 | * but the user is not actively waiting for that data, you should omit this flag; this will give 87 | * the OS additional freedom in scheduling your sync request. 88 | */ 89 | public static void TriggerRefresh() { 90 | Bundle b = new Bundle(); 91 | // Disable sync backoff and ignore sync preferences. In other words...perform sync NOW! 92 | b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); 93 | b.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); 94 | ContentResolver.requestSync( 95 | GenericAccountService.GetAccount(ACCOUNT_TYPE), // Sync account 96 | FeedContract.CONTENT_AUTHORITY, // Content authority 97 | b); // Extras 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Application/src/main/java/com/example/android/common/accounts/GenericAccountService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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.example.android.common.accounts; 18 | 19 | import android.accounts.AbstractAccountAuthenticator; 20 | import android.accounts.Account; 21 | import android.accounts.AccountAuthenticatorResponse; 22 | import android.accounts.NetworkErrorException; 23 | import android.app.Service; 24 | import android.content.Context; 25 | import android.content.Intent; 26 | import android.os.Bundle; 27 | import android.os.IBinder; 28 | import android.util.Log; 29 | 30 | public class GenericAccountService extends Service { 31 | private static final String TAG = "GenericAccountService"; 32 | public static final String ACCOUNT_NAME = "Account"; 33 | private Authenticator mAuthenticator; 34 | 35 | /** 36 | * Obtain a handle to the {@link android.accounts.Account} used for sync in this application. 37 | * 38 | *
It is important that the accountType specified here matches the value in your sync adapter 39 | * configuration XML file for android.accounts.AccountAuthenticator (often saved in 40 | * res/xml/syncadapter.xml). If this is not set correctly, you'll receive an error indicating 41 | * that "caller uid XXXXX is different than the authenticator's uid". 42 | * 43 | * @param accountType AccountType defined in the configuration XML file for 44 | * android.accounts.AccountAuthenticator (e.g. res/xml/syncadapter.xml). 45 | * @return Handle to application's account (not guaranteed to resolve unless CreateSyncAccount() 46 | * has been called) 47 | */ 48 | public static Account GetAccount(String accountType) { 49 | // Note: Normally the account name is set to the user's identity (username or email 50 | // address). However, since we aren't actually using any user accounts, it makes more sense 51 | // to use a generic string in this case. 52 | // 53 | // This string should *not* be localized. If the user switches locale, we would not be 54 | // able to locate the old account, and may erroneously register multiple accounts. 55 | final String accountName = ACCOUNT_NAME; 56 | return new Account(accountName, accountType); 57 | } 58 | 59 | @Override 60 | public void onCreate() { 61 | Log.i(TAG, "Service created"); 62 | mAuthenticator = new Authenticator(this); 63 | } 64 | 65 | @Override 66 | public void onDestroy() { 67 | Log.i(TAG, "Service destroyed"); 68 | } 69 | 70 | @Override 71 | public IBinder onBind(Intent intent) { 72 | return mAuthenticator.getIBinder(); 73 | } 74 | 75 | public class Authenticator extends AbstractAccountAuthenticator { 76 | public Authenticator(Context context) { 77 | super(context); 78 | } 79 | 80 | @Override 81 | public Bundle editProperties(AccountAuthenticatorResponse accountAuthenticatorResponse, 82 | String s) { 83 | throw new UnsupportedOperationException(); 84 | } 85 | 86 | @Override 87 | public Bundle addAccount(AccountAuthenticatorResponse accountAuthenticatorResponse, 88 | String s, String s2, String[] strings, Bundle bundle) 89 | throws NetworkErrorException { 90 | return null; 91 | } 92 | 93 | @Override 94 | public Bundle confirmCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse, 95 | Account account, Bundle bundle) 96 | throws NetworkErrorException { 97 | return null; 98 | } 99 | 100 | @Override 101 | public Bundle getAuthToken(AccountAuthenticatorResponse accountAuthenticatorResponse, 102 | Account account, String s, Bundle bundle) 103 | throws NetworkErrorException { 104 | throw new UnsupportedOperationException(); 105 | } 106 | 107 | @Override 108 | public String getAuthTokenLabel(String s) { 109 | throw new UnsupportedOperationException(); 110 | } 111 | 112 | @Override 113 | public Bundle updateCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse, 114 | Account account, String s, Bundle bundle) 115 | throws NetworkErrorException { 116 | throw new UnsupportedOperationException(); 117 | } 118 | 119 | @Override 120 | public Bundle hasFeatures(AccountAuthenticatorResponse accountAuthenticatorResponse, 121 | Account account, String[] strings) 122 | throws NetworkErrorException { 123 | throw new UnsupportedOperationException(); 124 | } 125 | } 126 | 127 | } 128 | 129 | -------------------------------------------------------------------------------- /Application/src/main/java/com/example/android/common/logger/LogView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 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.example.android.common.logger; 17 | 18 | import android.app.Activity; 19 | import android.content.Context; 20 | import android.util.*; 21 | import android.widget.TextView; 22 | 23 | /** Simple TextView which is used to output log data received through the LogNode interface. 24 | */ 25 | public class LogView extends TextView implements LogNode { 26 | 27 | public LogView(Context context) { 28 | super(context); 29 | } 30 | 31 | public LogView(Context context, AttributeSet attrs) { 32 | super(context, attrs); 33 | } 34 | 35 | public LogView(Context context, AttributeSet attrs, int defStyle) { 36 | super(context, attrs, defStyle); 37 | } 38 | 39 | /** 40 | * Formats the log data and prints it out to the LogView. 41 | * @param priority Log level of the data being logged. Verbose, Error, etc. 42 | * @param tag Tag for for the log data. Can be used to organize log statements. 43 | * @param msg The actual message to be logged. The actual message to be logged. 44 | * @param tr If an exception was thrown, this can be sent along for the logging facilities 45 | * to extract and print useful information. 46 | */ 47 | @Override 48 | public void println(int priority, String tag, String msg, Throwable tr) { 49 | 50 | 51 | String priorityStr = null; 52 | 53 | // For the purposes of this View, we want to print the priority as readable text. 54 | switch(priority) { 55 | case android.util.Log.VERBOSE: 56 | priorityStr = "VERBOSE"; 57 | break; 58 | case android.util.Log.DEBUG: 59 | priorityStr = "DEBUG"; 60 | break; 61 | case android.util.Log.INFO: 62 | priorityStr = "INFO"; 63 | break; 64 | case android.util.Log.WARN: 65 | priorityStr = "WARN"; 66 | break; 67 | case android.util.Log.ERROR: 68 | priorityStr = "ERROR"; 69 | break; 70 | case android.util.Log.ASSERT: 71 | priorityStr = "ASSERT"; 72 | break; 73 | default: 74 | break; 75 | } 76 | 77 | // Handily, the Log class has a facility for converting a stack trace into a usable string. 78 | String exceptionStr = null; 79 | if (tr != null) { 80 | exceptionStr = android.util.Log.getStackTraceString(tr); 81 | } 82 | 83 | // Take the priority, tag, message, and exception, and concatenate as necessary 84 | // into one usable line of text. 85 | final StringBuilder outputBuilder = new StringBuilder(); 86 | 87 | String delimiter = "\t"; 88 | appendIfNotNull(outputBuilder, priorityStr, delimiter); 89 | appendIfNotNull(outputBuilder, tag, delimiter); 90 | appendIfNotNull(outputBuilder, msg, delimiter); 91 | appendIfNotNull(outputBuilder, exceptionStr, delimiter); 92 | 93 | // In case this was originally called from an AsyncTask or some other off-UI thread, 94 | // make sure the update occurs within the UI thread. 95 | ((Activity) getContext()).runOnUiThread( (new Thread(new Runnable() { 96 | @Override 97 | public void run() { 98 | // Display the text we just generated within the LogView. 99 | appendToLog(outputBuilder.toString()); 100 | } 101 | }))); 102 | 103 | if (mNext != null) { 104 | mNext.println(priority, tag, msg, tr); 105 | } 106 | } 107 | 108 | public LogNode getNext() { 109 | return mNext; 110 | } 111 | 112 | public void setNext(LogNode node) { 113 | mNext = node; 114 | } 115 | 116 | /** Takes a string and adds to it, with a separator, if the bit to be added isn't null. Since 117 | * the logger takes so many arguments that might be null, this method helps cut out some of the 118 | * agonizing tedium of writing the same 3 lines over and over. 119 | * @param source StringBuilder containing the text to append to. 120 | * @param addStr The String to append 121 | * @param delimiter The String to separate the source and appended strings. A tab or comma, 122 | * for instance. 123 | * @return The fully concatenated String as a StringBuilder 124 | */ 125 | private StringBuilder appendIfNotNull(StringBuilder source, String addStr, String delimiter) { 126 | if (addStr != null) { 127 | if (addStr.length() == 0) { 128 | delimiter = ""; 129 | } 130 | 131 | return source.append(addStr).append(delimiter); 132 | } 133 | return source; 134 | } 135 | 136 | // The next LogNode in the chain. 137 | LogNode mNext; 138 | 139 | /** Outputs the string as a new line of log data in the LogView. */ 140 | public void appendToLog(String s) { 141 | append("\n" + s); 142 | } 143 | 144 | 145 | } 146 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /Application/src/main/java/com/example/android/common/logger/Log.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 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.example.android.common.logger; 17 | 18 | /** 19 | * Helper class for a list (or tree) of LoggerNodes. 20 | * 21 | *
When this is set as the head of the list, 22 | * an instance of it can function as a drop-in replacement for {@link android.util.Log}. 23 | * Most of the methods in this class server only to map a method call in Log to its equivalent 24 | * in LogNode.
25 | */ 26 | public class Log { 27 | // Grabbing the native values from Android's native logging facilities, 28 | // to make for easy migration and interop. 29 | public static final int NONE = -1; 30 | public static final int VERBOSE = android.util.Log.VERBOSE; 31 | public static final int DEBUG = android.util.Log.DEBUG; 32 | public static final int INFO = android.util.Log.INFO; 33 | public static final int WARN = android.util.Log.WARN; 34 | public static final int ERROR = android.util.Log.ERROR; 35 | public static final int ASSERT = android.util.Log.ASSERT; 36 | 37 | // Stores the beginning of the LogNode topology. 38 | private static LogNode mLogNode; 39 | 40 | /** 41 | * Returns the next LogNode in the linked list. 42 | */ 43 | public static LogNode getLogNode() { 44 | return mLogNode; 45 | } 46 | 47 | /** 48 | * Sets the LogNode data will be sent to. 49 | */ 50 | public static void setLogNode(LogNode node) { 51 | mLogNode = node; 52 | } 53 | 54 | /** 55 | * Instructs the LogNode to print the log data provided. Other LogNodes can 56 | * be chained to the end of the LogNode as desired. 57 | * 58 | * @param priority Log level of the data being logged. Verbose, Error, etc. 59 | * @param tag Tag for for the log data. Can be used to organize log statements. 60 | * @param msg The actual message to be logged. 61 | * @param tr If an exception was thrown, this can be sent along for the logging facilities 62 | * to extract and print useful information. 63 | */ 64 | public static void println(int priority, String tag, String msg, Throwable tr) { 65 | if (mLogNode != null) { 66 | mLogNode.println(priority, tag, msg, tr); 67 | } 68 | } 69 | 70 | /** 71 | * Instructs the LogNode to print the log data provided. Other LogNodes can 72 | * be chained to the end of the LogNode as desired. 73 | * 74 | * @param priority Log level of the data being logged. Verbose, Error, etc. 75 | * @param tag Tag for for the log data. Can be used to organize log statements. 76 | * @param msg The actual message to be logged. The actual message to be logged. 77 | */ 78 | public static void println(int priority, String tag, String msg) { 79 | println(priority, tag, msg, null); 80 | } 81 | 82 | /** 83 | * Prints a message at VERBOSE priority. 84 | * 85 | * @param tag Tag for for the log data. Can be used to organize log statements. 86 | * @param msg The actual message to be logged. 87 | * @param tr If an exception was thrown, this can be sent along for the logging facilities 88 | * to extract and print useful information. 89 | */ 90 | public static void v(String tag, String msg, Throwable tr) { 91 | println(VERBOSE, tag, msg, tr); 92 | } 93 | 94 | /** 95 | * Prints a message at VERBOSE priority. 96 | * 97 | * @param tag Tag for for the log data. Can be used to organize log statements. 98 | * @param msg The actual message to be logged. 99 | */ 100 | public static void v(String tag, String msg) { 101 | v(tag, msg, null); 102 | } 103 | 104 | 105 | /** 106 | * Prints a message at DEBUG priority. 107 | * 108 | * @param tag Tag for for the log data. Can be used to organize log statements. 109 | * @param msg The actual message to be logged. 110 | * @param tr If an exception was thrown, this can be sent along for the logging facilities 111 | * to extract and print useful information. 112 | */ 113 | public static void d(String tag, String msg, Throwable tr) { 114 | println(DEBUG, tag, msg, tr); 115 | } 116 | 117 | /** 118 | * Prints a message at DEBUG priority. 119 | * 120 | * @param tag Tag for for the log data. Can be used to organize log statements. 121 | * @param msg The actual message to be logged. 122 | */ 123 | public static void d(String tag, String msg) { 124 | d(tag, msg, null); 125 | } 126 | 127 | /** 128 | * Prints a message at INFO priority. 129 | * 130 | * @param tag Tag for for the log data. Can be used to organize log statements. 131 | * @param msg The actual message to be logged. 132 | * @param tr If an exception was thrown, this can be sent along for the logging facilities 133 | * to extract and print useful information. 134 | */ 135 | public static void i(String tag, String msg, Throwable tr) { 136 | println(INFO, tag, msg, tr); 137 | } 138 | 139 | /** 140 | * Prints a message at INFO priority. 141 | * 142 | * @param tag Tag for for the log data. Can be used to organize log statements. 143 | * @param msg The actual message to be logged. 144 | */ 145 | public static void i(String tag, String msg) { 146 | i(tag, msg, null); 147 | } 148 | 149 | /** 150 | * Prints a message at WARN priority. 151 | * 152 | * @param tag Tag for for the log data. Can be used to organize log statements. 153 | * @param msg The actual message to be logged. 154 | * @param tr If an exception was thrown, this can be sent along for the logging facilities 155 | * to extract and print useful information. 156 | */ 157 | public static void w(String tag, String msg, Throwable tr) { 158 | println(WARN, tag, msg, tr); 159 | } 160 | 161 | /** 162 | * Prints a message at WARN priority. 163 | * 164 | * @param tag Tag for for the log data. Can be used to organize log statements. 165 | * @param msg The actual message to be logged. 166 | */ 167 | public static void w(String tag, String msg) { 168 | w(tag, msg, null); 169 | } 170 | 171 | /** 172 | * Prints a message at WARN priority. 173 | * 174 | * @param tag Tag for for the log data. Can be used to organize log statements. 175 | * @param tr If an exception was thrown, this can be sent along for the logging facilities 176 | * to extract and print useful information. 177 | */ 178 | public static void w(String tag, Throwable tr) { 179 | w(tag, null, tr); 180 | } 181 | 182 | /** 183 | * Prints a message at ERROR priority. 184 | * 185 | * @param tag Tag for for the log data. Can be used to organize log statements. 186 | * @param msg The actual message to be logged. 187 | * @param tr If an exception was thrown, this can be sent along for the logging facilities 188 | * to extract and print useful information. 189 | */ 190 | public static void e(String tag, String msg, Throwable tr) { 191 | println(ERROR, tag, msg, tr); 192 | } 193 | 194 | /** 195 | * Prints a message at ERROR priority. 196 | * 197 | * @param tag Tag for for the log data. Can be used to organize log statements. 198 | * @param msg The actual message to be logged. 199 | */ 200 | public static void e(String tag, String msg) { 201 | e(tag, msg, null); 202 | } 203 | 204 | /** 205 | * Prints a message at ASSERT priority. 206 | * 207 | * @param tag Tag for for the log data. Can be used to organize log statements. 208 | * @param msg The actual message to be logged. 209 | * @param tr If an exception was thrown, this can be sent along for the logging facilities 210 | * to extract and print useful information. 211 | */ 212 | public static void wtf(String tag, String msg, Throwable tr) { 213 | println(ASSERT, tag, msg, tr); 214 | } 215 | 216 | /** 217 | * Prints a message at ASSERT priority. 218 | * 219 | * @param tag Tag for for the log data. Can be used to organize log statements. 220 | * @param msg The actual message to be logged. 221 | */ 222 | public static void wtf(String tag, String msg) { 223 | wtf(tag, msg, null); 224 | } 225 | 226 | /** 227 | * Prints a message at ASSERT priority. 228 | * 229 | * @param tag Tag for for the log data. Can be used to organize log statements. 230 | * @param tr If an exception was thrown, this can be sent along for the logging facilities 231 | * to extract and print useful information. 232 | */ 233 | public static void wtf(String tag, Throwable tr) { 234 | wtf(tag, null, tr); 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /Application/src/main/java/com/example/android/basicsyncadapter/provider/FeedProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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.example.android.basicsyncadapter.provider; 18 | 19 | import android.content.ContentProvider; 20 | import android.content.ContentValues; 21 | import android.content.Context; 22 | import android.content.UriMatcher; 23 | import android.database.Cursor; 24 | import android.database.sqlite.SQLiteDatabase; 25 | import android.database.sqlite.SQLiteOpenHelper; 26 | import android.net.Uri; 27 | 28 | import com.example.android.common.db.SelectionBuilder; 29 | 30 | public class FeedProvider extends ContentProvider { 31 | FeedDatabase mDatabaseHelper; 32 | 33 | /** 34 | * Content authority for this provider. 35 | */ 36 | private static final String AUTHORITY = FeedContract.CONTENT_AUTHORITY; 37 | 38 | // The constants below represent individual URI routes, as IDs. Every URI pattern recognized by 39 | // this ContentProvider is defined using sUriMatcher.addURI(), and associated with one of these 40 | // IDs. 41 | // 42 | // When a incoming URI is run through sUriMatcher, it will be tested against the defined 43 | // URI patterns, and the corresponding route ID will be returned. 44 | /** 45 | * URI ID for route: /entries 46 | */ 47 | public static final int ROUTE_ENTRIES = 1; 48 | 49 | /** 50 | * URI ID for route: /entries/{ID} 51 | */ 52 | public static final int ROUTE_ENTRIES_ID = 2; 53 | 54 | /** 55 | * UriMatcher, used to decode incoming URIs. 56 | */ 57 | private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); 58 | static { 59 | sUriMatcher.addURI(AUTHORITY, "entries", ROUTE_ENTRIES); 60 | sUriMatcher.addURI(AUTHORITY, "entries/*", ROUTE_ENTRIES_ID); 61 | } 62 | 63 | @Override 64 | public boolean onCreate() { 65 | mDatabaseHelper = new FeedDatabase(getContext()); 66 | return true; 67 | } 68 | 69 | /** 70 | * Determine the mime type for entries returned by a given URI. 71 | */ 72 | @Override 73 | public String getType(Uri uri) { 74 | final int match = sUriMatcher.match(uri); 75 | switch (match) { 76 | case ROUTE_ENTRIES: 77 | return FeedContract.Entry.CONTENT_TYPE; 78 | case ROUTE_ENTRIES_ID: 79 | return FeedContract.Entry.CONTENT_ITEM_TYPE; 80 | default: 81 | throw new UnsupportedOperationException("Unknown uri: " + uri); 82 | } 83 | } 84 | 85 | /** 86 | * Perform a database query by URI. 87 | * 88 | *Currently supports returning all entries (/entries) and individual entries by ID 89 | * (/entries/{ID}). 90 | */ 91 | @Override 92 | public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 93 | String sortOrder) { 94 | SQLiteDatabase db = mDatabaseHelper.getReadableDatabase(); 95 | SelectionBuilder builder = new SelectionBuilder(); 96 | int uriMatch = sUriMatcher.match(uri); 97 | switch (uriMatch) { 98 | case ROUTE_ENTRIES_ID: 99 | // Return a single entry, by ID. 100 | String id = uri.getLastPathSegment(); 101 | builder.where(FeedContract.Entry._ID + "=?", id); 102 | case ROUTE_ENTRIES: 103 | // Return all known entries. 104 | builder.table(FeedContract.Entry.TABLE_NAME) 105 | .where(selection, selectionArgs); 106 | Cursor c = builder.query(db, projection, sortOrder); 107 | // Note: Notification URI must be manually set here for loaders to correctly 108 | // register ContentObservers. 109 | Context ctx = getContext(); 110 | assert ctx != null; 111 | c.setNotificationUri(ctx.getContentResolver(), uri); 112 | return c; 113 | default: 114 | throw new UnsupportedOperationException("Unknown uri: " + uri); 115 | } 116 | } 117 | 118 | /** 119 | * Insert a new entry into the database. 120 | */ 121 | @Override 122 | public Uri insert(Uri uri, ContentValues values) { 123 | final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase(); 124 | assert db != null; 125 | final int match = sUriMatcher.match(uri); 126 | Uri result; 127 | switch (match) { 128 | case ROUTE_ENTRIES: 129 | long id = db.insertOrThrow(FeedContract.Entry.TABLE_NAME, null, values); 130 | result = Uri.parse(FeedContract.Entry.CONTENT_URI + "/" + id); 131 | break; 132 | case ROUTE_ENTRIES_ID: 133 | throw new UnsupportedOperationException("Insert not supported on URI: " + uri); 134 | default: 135 | throw new UnsupportedOperationException("Unknown uri: " + uri); 136 | } 137 | // Send broadcast to registered ContentObservers, to refresh UI. 138 | Context ctx = getContext(); 139 | assert ctx != null; 140 | ctx.getContentResolver().notifyChange(uri, null, false); 141 | return result; 142 | } 143 | 144 | /** 145 | * Delete an entry by database by URI. 146 | */ 147 | @Override 148 | public int delete(Uri uri, String selection, String[] selectionArgs) { 149 | SelectionBuilder builder = new SelectionBuilder(); 150 | final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase(); 151 | final int match = sUriMatcher.match(uri); 152 | int count; 153 | switch (match) { 154 | case ROUTE_ENTRIES: 155 | count = builder.table(FeedContract.Entry.TABLE_NAME) 156 | .where(selection, selectionArgs) 157 | .delete(db); 158 | break; 159 | case ROUTE_ENTRIES_ID: 160 | String id = uri.getLastPathSegment(); 161 | count = builder.table(FeedContract.Entry.TABLE_NAME) 162 | .where(FeedContract.Entry._ID + "=?", id) 163 | .where(selection, selectionArgs) 164 | .delete(db); 165 | break; 166 | default: 167 | throw new UnsupportedOperationException("Unknown uri: " + uri); 168 | } 169 | // Send broadcast to registered ContentObservers, to refresh UI. 170 | Context ctx = getContext(); 171 | assert ctx != null; 172 | ctx.getContentResolver().notifyChange(uri, null, false); 173 | return count; 174 | } 175 | 176 | /** 177 | * Update an etry in the database by URI. 178 | */ 179 | @Override 180 | public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 181 | SelectionBuilder builder = new SelectionBuilder(); 182 | final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase(); 183 | final int match = sUriMatcher.match(uri); 184 | int count; 185 | switch (match) { 186 | case ROUTE_ENTRIES: 187 | count = builder.table(FeedContract.Entry.TABLE_NAME) 188 | .where(selection, selectionArgs) 189 | .update(db, values); 190 | break; 191 | case ROUTE_ENTRIES_ID: 192 | String id = uri.getLastPathSegment(); 193 | count = builder.table(FeedContract.Entry.TABLE_NAME) 194 | .where(FeedContract.Entry._ID + "=?", id) 195 | .where(selection, selectionArgs) 196 | .update(db, values); 197 | break; 198 | default: 199 | throw new UnsupportedOperationException("Unknown uri: " + uri); 200 | } 201 | Context ctx = getContext(); 202 | assert ctx != null; 203 | ctx.getContentResolver().notifyChange(uri, null, false); 204 | return count; 205 | } 206 | 207 | /** 208 | * SQLite backend for @{link FeedProvider}. 209 | * 210 | * Provides access to an disk-backed, SQLite datastore which is utilized by FeedProvider. This 211 | * database should never be accessed by other parts of the application directly. 212 | */ 213 | static class FeedDatabase extends SQLiteOpenHelper { 214 | /** Schema version. */ 215 | public static final int DATABASE_VERSION = 1; 216 | /** Filename for SQLite file. */ 217 | public static final String DATABASE_NAME = "feed.db"; 218 | 219 | private static final String TYPE_TEXT = " TEXT"; 220 | private static final String TYPE_INTEGER = " INTEGER"; 221 | private static final String COMMA_SEP = ","; 222 | /** SQL statement to create "entry" table. */ 223 | private static final String SQL_CREATE_ENTRIES = 224 | "CREATE TABLE " + FeedContract.Entry.TABLE_NAME + " (" + 225 | FeedContract.Entry._ID + " INTEGER PRIMARY KEY," + 226 | FeedContract.Entry.COLUMN_NAME_ENTRY_ID + TYPE_TEXT + COMMA_SEP + 227 | FeedContract.Entry.COLUMN_NAME_TITLE + TYPE_TEXT + COMMA_SEP + 228 | FeedContract.Entry.COLUMN_NAME_LINK + TYPE_TEXT + COMMA_SEP + 229 | FeedContract.Entry.COLUMN_NAME_PUBLISHED + TYPE_INTEGER + ")"; 230 | 231 | /** SQL statement to drop "entry" table. */ 232 | private static final String SQL_DELETE_ENTRIES = 233 | "DROP TABLE IF EXISTS " + FeedContract.Entry.TABLE_NAME; 234 | 235 | public FeedDatabase(Context context) { 236 | super(context, DATABASE_NAME, null, DATABASE_VERSION); 237 | } 238 | 239 | @Override 240 | public void onCreate(SQLiteDatabase db) { 241 | db.execSQL(SQL_CREATE_ENTRIES); 242 | } 243 | 244 | @Override 245 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 246 | // This database is only a cache for online data, so its upgrade policy is 247 | // to simply to discard the data and start over 248 | db.execSQL(SQL_DELETE_ENTRIES); 249 | onCreate(db); 250 | } 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /Application/src/main/java/com/example/android/basicsyncadapter/net/FeedParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 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.example.android.basicsyncadapter.net; 18 | 19 | import android.text.format.Time; 20 | import android.util.Xml; 21 | 22 | import org.xmlpull.v1.XmlPullParser; 23 | import org.xmlpull.v1.XmlPullParserException; 24 | 25 | import java.io.IOException; 26 | import java.io.InputStream; 27 | import java.text.ParseException; 28 | import java.util.ArrayList; 29 | import java.util.List; 30 | 31 | /** 32 | * This class parses generic Atom feeds. 33 | * 34 | *
Given an InputStream representation of a feed, it returns a List of entries, 35 | * where each list element represents a single entry (post) in the XML feed. 36 | * 37 | *
An example of an Atom feed can be found at:
38 | * http://en.wikipedia.org/w/index.php?title=Atom_(standard)&oldid=560239173#Example_of_an_Atom_1.0_feed
39 | */
40 | public class FeedParser {
41 |
42 | // Constants indicting XML element names that we're interested in
43 | private static final int TAG_ID = 1;
44 | private static final int TAG_TITLE = 2;
45 | private static final int TAG_PUBLISHED = 3;
46 | private static final int TAG_LINK = 4;
47 |
48 | // We don't use XML namespaces
49 | private static final String ns = null;
50 |
51 | /** Parse an Atom feed, returning a collection of Entry objects.
52 | *
53 | * @param in Atom feed, as a stream.
54 | * @return List of {@link com.example.android.basicsyncadapter.net.FeedParser.Entry} objects.
55 | * @throws org.xmlpull.v1.XmlPullParserException on error parsing feed.
56 | * @throws java.io.IOException on I/O error.
57 | */
58 | public List You probably want to call readTag().
192 | *
193 | * @param parser Current parser object
194 | * @param tag XML element tag name to parse
195 | * @return Body of the specified tag
196 | * @throws java.io.IOException
197 | * @throws org.xmlpull.v1.XmlPullParserException
198 | */
199 | private String readBasicTag(XmlPullParser parser, String tag)
200 | throws IOException, XmlPullParserException {
201 | parser.require(XmlPullParser.START_TAG, ns, tag);
202 | String result = readText(parser);
203 | parser.require(XmlPullParser.END_TAG, ns, tag);
204 | return result;
205 | }
206 |
207 | /**
208 | * Processes link tags in the feed.
209 | */
210 | private String readAlternateLink(XmlPullParser parser)
211 | throws IOException, XmlPullParserException {
212 | String link = null;
213 | parser.require(XmlPullParser.START_TAG, ns, "link");
214 | String tag = parser.getName();
215 | String relType = parser.getAttributeValue(null, "rel");
216 | if (relType.equals("alternate")) {
217 | link = parser.getAttributeValue(null, "href");
218 | }
219 | while (true) {
220 | if (parser.nextTag() == XmlPullParser.END_TAG) break;
221 | // Intentionally break; consumes any remaining sub-tags.
222 | }
223 | return link;
224 | }
225 |
226 | /**
227 | * For the tags title and summary, extracts their text values.
228 | */
229 | private String readText(XmlPullParser parser) throws IOException, XmlPullParserException {
230 | String result = null;
231 | if (parser.next() == XmlPullParser.TEXT) {
232 | result = parser.getText();
233 | parser.nextTag();
234 | }
235 | return result;
236 | }
237 |
238 | /**
239 | * Skips tags the parser isn't interested in. Uses depth to handle nested tags. i.e.,
240 | * if the next tag after a START_TAG isn't a matching END_TAG, it keeps going until it
241 | * finds the matching END_TAG (as indicated by the value of "depth" being 0).
242 | */
243 | private void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
244 | if (parser.getEventType() != XmlPullParser.START_TAG) {
245 | throw new IllegalStateException();
246 | }
247 | int depth = 1;
248 | while (depth != 0) {
249 | switch (parser.next()) {
250 | case XmlPullParser.END_TAG:
251 | depth--;
252 | break;
253 | case XmlPullParser.START_TAG:
254 | depth++;
255 | break;
256 | }
257 | }
258 | }
259 |
260 | /**
261 | * This class represents a single entry (post) in the XML feed.
262 | *
263 | * It includes the data members "title," "link," and "summary."
264 | */
265 | public static class Entry {
266 | public final String id;
267 | public final String title;
268 | public final String link;
269 | public final long published;
270 |
271 | Entry(String id, String title, String link, long published) {
272 | this.id = id;
273 | this.title = title;
274 | this.link = link;
275 | this.published = published;
276 | }
277 | }
278 | }
279 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | --------------
3 |
4 | Version 2.0, January 2004
5 | http://www.apache.org/licenses/
6 |
7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
8 |
9 | 1. Definitions.
10 |
11 | "License" shall mean the terms and conditions for use, reproduction,
12 | and distribution as defined by Sections 1 through 9 of this document.
13 |
14 | "Licensor" shall mean the copyright owner or entity authorized by
15 | the copyright owner that is granting the License.
16 |
17 | "Legal Entity" shall mean the union of the acting entity and all
18 | other entities that control, are controlled by, or are under common
19 | control with that entity. For the purposes of this definition,
20 | "control" means (i) the power, direct or indirect, to cause the
21 | direction or management of such entity, whether by contract or
22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
23 | outstanding shares, or (iii) beneficial ownership of such entity.
24 |
25 | "You" (or "Your") shall mean an individual or Legal Entity
26 | exercising permissions granted by this License.
27 |
28 | "Source" form shall mean the preferred form for making modifications,
29 | including but not limited to software source code, documentation
30 | source, and configuration files.
31 |
32 | "Object" form shall mean any form resulting from mechanical
33 | transformation or translation of a Source form, including but
34 | not limited to compiled object code, generated documentation,
35 | and conversions to other media types.
36 |
37 | "Work" shall mean the work of authorship, whether in Source or
38 | Object form, made available under the License, as indicated by a
39 | copyright notice that is included in or attached to the work
40 | (an example is provided in the Appendix below).
41 |
42 | "Derivative Works" shall mean any work, whether in Source or Object
43 | form, that is based on (or derived from) the Work and for which the
44 | editorial revisions, annotations, elaborations, or other modifications
45 | represent, as a whole, an original work of authorship. For the purposes
46 | of this License, Derivative Works shall not include works that remain
47 | separable from, or merely link (or bind by name) to the interfaces of,
48 | the Work and Derivative Works thereof.
49 |
50 | "Contribution" shall mean any work of authorship, including
51 | the original version of the Work and any modifications or additions
52 | to that Work or Derivative Works thereof, that is intentionally
53 | submitted to Licensor for inclusion in the Work by the copyright owner
54 | or by an individual or Legal Entity authorized to submit on behalf of
55 | the copyright owner. For the purposes of this definition, "submitted"
56 | means any form of electronic, verbal, or written communication sent
57 | to the Licensor or its representatives, including but not limited to
58 | communication on electronic mailing lists, source code control systems,
59 | and issue tracking systems that are managed by, or on behalf of, the
60 | Licensor for the purpose of discussing and improving the Work, but
61 | excluding communication that is conspicuously marked or otherwise
62 | designated in writing by the copyright owner as "Not a Contribution."
63 |
64 | "Contributor" shall mean Licensor and any individual or Legal Entity
65 | on behalf of whom a Contribution has been received by Licensor and
66 | subsequently incorporated within the Work.
67 |
68 | 2. Grant of Copyright License. Subject to the terms and conditions of
69 | this License, each Contributor hereby grants to You a perpetual,
70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
71 | copyright license to reproduce, prepare Derivative Works of,
72 | publicly display, publicly perform, sublicense, and distribute the
73 | Work and such Derivative Works in Source or Object form.
74 |
75 | 3. Grant of Patent License. Subject to the terms and conditions of
76 | this License, each Contributor hereby grants to You a perpetual,
77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
78 | (except as stated in this section) patent license to make, have made,
79 | use, offer to sell, sell, import, and otherwise transfer the Work,
80 | where such license applies only to those patent claims licensable
81 | by such Contributor that are necessarily infringed by their
82 | Contribution(s) alone or by combination of their Contribution(s)
83 | with the Work to which such Contribution(s) was submitted. If You
84 | institute patent litigation against any entity (including a
85 | cross-claim or counterclaim in a lawsuit) alleging that the Work
86 | or a Contribution incorporated within the Work constitutes direct
87 | or contributory patent infringement, then any patent licenses
88 | granted to You under this License for that Work shall terminate
89 | as of the date such litigation is filed.
90 |
91 | 4. Redistribution. You may reproduce and distribute copies of the
92 | Work or Derivative Works thereof in any medium, with or without
93 | modifications, and in Source or Object form, provided that You
94 | meet the following conditions:
95 |
96 | (a) You must give any other recipients of the Work or
97 | Derivative Works a copy of this License; and
98 |
99 | (b) You must cause any modified files to carry prominent notices
100 | stating that You changed the files; and
101 |
102 | (c) You must retain, in the Source form of any Derivative Works
103 | that You distribute, all copyright, patent, trademark, and
104 | attribution notices from the Source form of the Work,
105 | excluding those notices that do not pertain to any part of
106 | the Derivative Works; and
107 |
108 | (d) If the Work includes a "NOTICE" text file as part of its
109 | distribution, then any Derivative Works that You distribute must
110 | include a readable copy of the attribution notices contained
111 | within such NOTICE file, excluding those notices that do not
112 | pertain to any part of the Derivative Works, in at least one
113 | of the following places: within a NOTICE text file distributed
114 | as part of the Derivative Works; within the Source form or
115 | documentation, if provided along with the Derivative Works; or,
116 | within a display generated by the Derivative Works, if and
117 | wherever such third-party notices normally appear. The contents
118 | of the NOTICE file are for informational purposes only and
119 | do not modify the License. You may add Your own attribution
120 | notices within Derivative Works that You distribute, alongside
121 | or as an addendum to the NOTICE text from the Work, provided
122 | that such additional attribution notices cannot be construed
123 | as modifying the License.
124 |
125 | You may add Your own copyright statement to Your modifications and
126 | may provide additional or different license terms and conditions
127 | for use, reproduction, or distribution of Your modifications, or
128 | for any such Derivative Works as a whole, provided Your use,
129 | reproduction, and distribution of the Work otherwise complies with
130 | the conditions stated in this License.
131 |
132 | 5. Submission of Contributions. Unless You explicitly state otherwise,
133 | any Contribution intentionally submitted for inclusion in the Work
134 | by You to the Licensor shall be under the terms and conditions of
135 | this License, without any additional terms or conditions.
136 | Notwithstanding the above, nothing herein shall supersede or modify
137 | the terms of any separate license agreement you may have executed
138 | with Licensor regarding such Contributions.
139 |
140 | 6. Trademarks. This License does not grant permission to use the trade
141 | names, trademarks, service marks, or product names of the Licensor,
142 | except as required for reasonable and customary use in describing the
143 | origin of the Work and reproducing the content of the NOTICE file.
144 |
145 | 7. Disclaimer of Warranty. Unless required by applicable law or
146 | agreed to in writing, Licensor provides the Work (and each
147 | Contributor provides its Contributions) on an "AS IS" BASIS,
148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
149 | implied, including, without limitation, any warranties or conditions
150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
151 | PARTICULAR PURPOSE. You are solely responsible for determining the
152 | appropriateness of using or redistributing the Work and assume any
153 | risks associated with Your exercise of permissions under this License.
154 |
155 | 8. Limitation of Liability. In no event and under no legal theory,
156 | whether in tort (including negligence), contract, or otherwise,
157 | unless required by applicable law (such as deliberate and grossly
158 | negligent acts) or agreed to in writing, shall any Contributor be
159 | liable to You for damages, including any direct, indirect, special,
160 | incidental, or consequential damages of any character arising as a
161 | result of this License or out of the use or inability to use the
162 | Work (including but not limited to damages for loss of goodwill,
163 | work stoppage, computer failure or malfunction, or any and all
164 | other commercial damages or losses), even if such Contributor
165 | has been advised of the possibility of such damages.
166 |
167 | 9. Accepting Warranty or Additional Liability. While redistributing
168 | the Work or Derivative Works thereof, You may choose to offer,
169 | and charge a fee for, acceptance of support, warranty, indemnity,
170 | or other liability obligations and/or rights consistent with this
171 | License. However, in accepting such obligations, You may act only
172 | on Your own behalf and on Your sole responsibility, not on behalf
173 | of any other Contributor, and only if You agree to indemnify,
174 | defend, and hold each Contributor harmless for any liability
175 | incurred by, or claims asserted against, such Contributor by reason
176 | of your accepting any such warranty or additional liability.
177 |
178 | END OF TERMS AND CONDITIONS
179 |
180 | APPENDIX: How to apply the Apache License to your work.
181 |
182 | To apply the Apache License to your work, attach the following
183 | boilerplate notice, with the fields enclosed by brackets "{}"
184 | replaced with your own identifying information. (Don't include
185 | the brackets!) The text should be enclosed in the appropriate
186 | comment syntax for the file format. We also recommend that a
187 | file or class name and description of purpose be included on the
188 | same "printed page" as the copyright notice for easier
189 | identification within third-party archives.
190 |
191 | Copyright {yyyy} {name of copyright owner}
192 |
193 | Licensed under the Apache License, Version 2.0 (the "License");
194 | you may not use this file except in compliance with the License.
195 | You may obtain a copy of the License at
196 |
197 | http://www.apache.org/licenses/LICENSE-2.0
198 |
199 | Unless required by applicable law or agreed to in writing, software
200 | distributed under the License is distributed on an "AS IS" BASIS,
201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
202 | See the License for the specific language governing permissions and
203 | limitations under the License.
204 |
--------------------------------------------------------------------------------
/Application/src/main/java/com/example/android/basicsyncadapter/SyncAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 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.example.android.basicsyncadapter;
18 |
19 | import android.accounts.Account;
20 | import android.annotation.TargetApi;
21 | import android.content.AbstractThreadedSyncAdapter;
22 | import android.content.ContentProviderClient;
23 | import android.content.ContentProviderOperation;
24 | import android.content.ContentResolver;
25 | import android.content.Context;
26 | import android.content.OperationApplicationException;
27 | import android.content.SyncResult;
28 | import android.database.Cursor;
29 | import android.net.Uri;
30 | import android.os.Build;
31 | import android.os.Bundle;
32 | import android.os.RemoteException;
33 | import android.util.Log;
34 |
35 | import com.example.android.basicsyncadapter.net.FeedParser;
36 | import com.example.android.basicsyncadapter.provider.FeedContract;
37 |
38 | import org.xmlpull.v1.XmlPullParserException;
39 |
40 | import java.io.IOException;
41 | import java.io.InputStream;
42 | import java.net.HttpURLConnection;
43 | import java.net.MalformedURLException;
44 | import java.net.URL;
45 | import java.text.ParseException;
46 | import java.util.ArrayList;
47 | import java.util.HashMap;
48 | import java.util.List;
49 |
50 | /**
51 | * Define a sync adapter for the app.
52 | *
53 | * This class is instantiated in {@link SyncService}, which also binds SyncAdapter to the system.
54 | * SyncAdapter should only be initialized in SyncService, never anywhere else.
55 | *
56 | * The system calls onPerformSync() via an RPC call through the IBinder object supplied by
57 | * SyncService.
58 | */
59 | class SyncAdapter extends AbstractThreadedSyncAdapter {
60 | public static final String TAG = "SyncAdapter";
61 |
62 | /**
63 | * URL to fetch content from during a sync.
64 | *
65 | * This points to the Android Developers Blog. (Side note: We highly recommend reading the
66 | * Android Developer Blog to stay up to date on the latest Android platform developments!)
67 | */
68 | private static final String FEED_URL = "http://android-developers.blogspot.com/atom.xml";
69 |
70 | /**
71 | * Network connection timeout, in milliseconds.
72 | */
73 | private static final int NET_CONNECT_TIMEOUT_MILLIS = 15000; // 15 seconds
74 |
75 | /**
76 | * Network read timeout, in milliseconds.
77 | */
78 | private static final int NET_READ_TIMEOUT_MILLIS = 10000; // 10 seconds
79 |
80 | /**
81 | * Content resolver, for performing database operations.
82 | */
83 | private final ContentResolver mContentResolver;
84 |
85 | /**
86 | * Project used when querying content provider. Returns all known fields.
87 | */
88 | private static final String[] PROJECTION = new String[] {
89 | FeedContract.Entry._ID,
90 | FeedContract.Entry.COLUMN_NAME_ENTRY_ID,
91 | FeedContract.Entry.COLUMN_NAME_TITLE,
92 | FeedContract.Entry.COLUMN_NAME_LINK,
93 | FeedContract.Entry.COLUMN_NAME_PUBLISHED};
94 |
95 | // Constants representing column positions from PROJECTION.
96 | public static final int COLUMN_ID = 0;
97 | public static final int COLUMN_ENTRY_ID = 1;
98 | public static final int COLUMN_TITLE = 2;
99 | public static final int COLUMN_LINK = 3;
100 | public static final int COLUMN_PUBLISHED = 4;
101 |
102 | /**
103 | * Constructor. Obtains handle to content resolver for later use.
104 | */
105 | public SyncAdapter(Context context, boolean autoInitialize) {
106 | super(context, autoInitialize);
107 | mContentResolver = context.getContentResolver();
108 | }
109 |
110 | /**
111 | * Constructor. Obtains handle to content resolver for later use.
112 | */
113 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
114 | public SyncAdapter(Context context, boolean autoInitialize, boolean allowParallelSyncs) {
115 | super(context, autoInitialize, allowParallelSyncs);
116 | mContentResolver = context.getContentResolver();
117 | }
118 |
119 | /**
120 | * Called by the Android system in response to a request to run the sync adapter. The work
121 | * required to read data from the network, parse it, and store it in the content provider is
122 | * done here. Extending AbstractThreadedSyncAdapter ensures that all methods within SyncAdapter
123 | * run on a background thread. For this reason, blocking I/O and other long-running tasks can be
124 | * run in situ, and you don't have to set up a separate thread for them.
125 | .
126 | *
127 | * This is where we actually perform any work required to perform a sync.
128 | * {@link android.content.AbstractThreadedSyncAdapter} guarantees that this will be called on a non-UI thread,
129 | * so it is safe to peform blocking I/O here.
130 | *
131 | * The syncResult argument allows you to pass information back to the method that triggered
132 | * the sync.
133 | */
134 | @Override
135 | public void onPerformSync(Account account, Bundle extras, String authority,
136 | ContentProviderClient provider, SyncResult syncResult) {
137 | Log.i(TAG, "Beginning network synchronization");
138 | try {
139 | final URL location = new URL(FEED_URL);
140 | InputStream stream = null;
141 |
142 | try {
143 | Log.i(TAG, "Streaming data from network: " + location);
144 | stream = downloadUrl(location);
145 | updateLocalFeedData(stream, syncResult);
146 | // Makes sure that the InputStream is closed after the app is
147 | // finished using it.
148 | } finally {
149 | if (stream != null) {
150 | stream.close();
151 | }
152 | }
153 | } catch (MalformedURLException e) {
154 | Log.e(TAG, "Feed URL is malformed", e);
155 | syncResult.stats.numParseExceptions++;
156 | return;
157 | } catch (IOException e) {
158 | Log.e(TAG, "Error reading from network: " + e.toString());
159 | syncResult.stats.numIoExceptions++;
160 | return;
161 | } catch (XmlPullParserException e) {
162 | Log.e(TAG, "Error parsing feed: " + e.toString());
163 | syncResult.stats.numParseExceptions++;
164 | return;
165 | } catch (ParseException e) {
166 | Log.e(TAG, "Error parsing feed: " + e.toString());
167 | syncResult.stats.numParseExceptions++;
168 | return;
169 | } catch (RemoteException e) {
170 | Log.e(TAG, "Error updating database: " + e.toString());
171 | syncResult.databaseError = true;
172 | return;
173 | } catch (OperationApplicationException e) {
174 | Log.e(TAG, "Error updating database: " + e.toString());
175 | syncResult.databaseError = true;
176 | return;
177 | }
178 | Log.i(TAG, "Network synchronization complete");
179 | }
180 |
181 | /**
182 | * Read XML from an input stream, storing it into the content provider.
183 | *
184 | * This is where incoming data is persisted, committing the results of a sync. In order to
185 | * minimize (expensive) disk operations, we compare incoming data with what's already in our
186 | * database, and compute a merge. Only changes (insert/update/delete) will result in a database
187 | * write.
188 | *
189 | * As an additional optimization, we use a batch operation to perform all database writes at
190 | * once.
191 | *
192 | * Merge strategy:
193 | * 1. Get cursor to all items in feed Database access is mediated by a content provider, specified in
50 | * {@link com.example.android.basicsyncadapter.provider.FeedProvider}. This content
51 | * provider is
52 | * automatically populated by {@link SyncService}.
53 | *
54 | * Selecting an item from the displayed list displays the article in the default browser.
55 | *
56 | * If the content provider doesn't return any data, then the first sync hasn't run yet. This sync
57 | * adapter assumes data exists in the provider once a sync has run. If your app doesn't work like
58 | * this, you should add a flag that notes if a sync has run, so you can differentiate between "no
59 | * available data" and "no initial sync", and display this in the UI.
60 | *
61 | * The ActionBar displays a "Refresh" button. When the user clicks "Refresh", the sync adapter
62 | * runs immediately. An indeterminate ProgressBar element is displayed, showing that the sync is
63 | * occurring.
64 | */
65 | public class EntryListFragment extends ListFragment
66 | implements LoaderManager.LoaderCallbacks This allows us to delete our SyncObserver once the application is no longer in the
80 | * foreground.
81 | */
82 | private Object mSyncObserverHandle;
83 |
84 | /**
85 | * Options menu used to populate ActionBar.
86 | */
87 | private Menu mOptionsMenu;
88 |
89 | /**
90 | * Projection for querying the content provider.
91 | */
92 | private static final String[] PROJECTION = new String[]{
93 | FeedContract.Entry._ID,
94 | FeedContract.Entry.COLUMN_NAME_TITLE,
95 | FeedContract.Entry.COLUMN_NAME_LINK,
96 | FeedContract.Entry.COLUMN_NAME_PUBLISHED
97 | };
98 |
99 | // Column indexes. The index of a column in the Cursor is the same as its relative position in
100 | // the projection.
101 | /** Column index for _ID */
102 | private static final int COLUMN_ID = 0;
103 | /** Column index for title */
104 | private static final int COLUMN_TITLE = 1;
105 | /** Column index for link */
106 | private static final int COLUMN_URL_STRING = 2;
107 | /** Column index for published */
108 | private static final int COLUMN_PUBLISHED = 3;
109 |
110 | /**
111 | * List of Cursor columns to read from when preparing an adapter to populate the ListView.
112 | */
113 | private static final String[] FROM_COLUMNS = new String[]{
114 | FeedContract.Entry.COLUMN_NAME_TITLE,
115 | FeedContract.Entry.COLUMN_NAME_PUBLISHED
116 | };
117 |
118 | /**
119 | * List of Views which will be populated by Cursor data.
120 | */
121 | private static final int[] TO_FIELDS = new int[]{
122 | android.R.id.text1,
123 | android.R.id.text2};
124 |
125 | /**
126 | * Mandatory empty constructor for the fragment manager to instantiate the
127 | * fragment (e.g. upon screen orientation changes).
128 | */
129 | public EntryListFragment() {}
130 |
131 | @Override
132 | public void onCreate(Bundle savedInstanceState) {
133 | super.onCreate(savedInstanceState);
134 | setHasOptionsMenu(true);
135 | }
136 |
137 | /**
138 | * Create SyncAccount at launch, if needed.
139 | *
140 | * This will create a new account with the system for our application, register our
141 | * {@link SyncService} with it, and establish a sync schedule.
142 | */
143 | @Override
144 | public void onAttach(Activity activity) {
145 | super.onAttach(activity);
146 |
147 | // Create account, if needed
148 | SyncUtils.CreateSyncAccount(activity);
149 | }
150 |
151 | @Override
152 | public void onViewCreated(View view, Bundle savedInstanceState) {
153 | super.onViewCreated(view, savedInstanceState);
154 |
155 | mAdapter = new SimpleCursorAdapter(
156 | getActivity(), // Current context
157 | android.R.layout.simple_list_item_activated_2, // Layout for individual rows
158 | null, // Cursor
159 | FROM_COLUMNS, // Cursor columns to use
160 | TO_FIELDS, // Layout fields to use
161 | 0 // No flags
162 | );
163 | mAdapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
164 | @Override
165 | public boolean setViewValue(View view, Cursor cursor, int i) {
166 | if (i == COLUMN_PUBLISHED) {
167 | // Convert timestamp to human-readable date
168 | Time t = new Time();
169 | t.set(cursor.getLong(i));
170 | ((TextView) view).setText(t.format("%Y-%m-%d %H:%M"));
171 | return true;
172 | } else {
173 | // Let SimpleCursorAdapter handle other fields automatically
174 | return false;
175 | }
176 | }
177 | });
178 | setListAdapter(mAdapter);
179 | setEmptyText(getText(R.string.loading));
180 | getLoaderManager().initLoader(0, null, this);
181 | }
182 |
183 | @Override
184 | public void onResume() {
185 | super.onResume();
186 | mSyncStatusObserver.onStatusChanged(0);
187 |
188 | // Watch for sync state changes
189 | final int mask = ContentResolver.SYNC_OBSERVER_TYPE_PENDING |
190 | ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE;
191 | mSyncObserverHandle = ContentResolver.addStatusChangeListener(mask, mSyncStatusObserver);
192 | }
193 |
194 | @Override
195 | public void onPause() {
196 | super.onPause();
197 | if (mSyncObserverHandle != null) {
198 | ContentResolver.removeStatusChangeListener(mSyncObserverHandle);
199 | mSyncObserverHandle = null;
200 | }
201 | }
202 |
203 | /**
204 | * Query the content provider for data.
205 | *
206 | * Loaders do queries in a background thread. They also provide a ContentObserver that is
207 | * triggered when data in the content provider changes. When the sync adapter updates the
208 | * content provider, the ContentObserver responds by resetting the loader and then reloading
209 | * it.
210 | */
211 | @Override
212 | public Loader This class provides a convenient frontend for working with SQL. Instead of composing statements
41 | * manually using string concatenation, method calls are used to construct the statement one
42 | * clause at a time. These methods can be chained together.
43 | *
44 | * If multiple where() statements are provided, they're combined using {@code AND}.
45 | *
46 | * Example:
47 | *
48 | * In this example, the table name and filters ({@code WHERE} clauses) are both explicitly
57 | * specified via method call. SelectionBuilder takes care of issuing a "query" command to the
58 | * database, and returns the resulting {@link Cursor} object.
59 | *
60 | * Inner {@code JOIN}s can be accomplished using the mapToTable() function. The map() function
61 | * can be used to create new columns based on arbitrary (SQL-based) criteria. In advanced usage,
62 | * entire subqueries can be passed into the map() function.
63 | *
64 | * Advanced example:
65 | *
66 | * In this example, we have two different types of {@code JOIN}s: a left outer join using a
87 | * modified table name (since this class doesn't directly support these), and an inner join using
88 | * the mapToTable() function. The map() function is used to insert a count based on specific
89 | * criteria, executed as a sub-query.
90 | *
91 | * This class is not thread safe.
92 | */
93 | public class SelectionBuilder {
94 | private static final String TAG = "basicsyncadapter";
95 |
96 | private String mTable = null;
97 | private Map Calling this method is more efficient than creating a new SelectionBuilder object.
105 | *
106 | * @return Fluent interface
107 | */
108 | public SelectionBuilder reset() {
109 | mTable = null;
110 | mSelection.setLength(0);
111 | mSelectionArgs.clear();
112 | return this;
113 | }
114 |
115 | /**
116 | * Append the given selection clause to the internal state. Each clause is
117 | * surrounded with parenthesis and combined using {@code AND}.
118 | *
119 | * In the most basic usage, simply provide a selection in SQL {@code WHERE} statement format.
120 | *
121 | * Example:
122 | *
123 | * User input should never be directly supplied as as part of the selection statement.
128 | * Instead, use positional parameters in your selection statement, then pass the user input
129 | * in via the selectionArgs parameter. This prevents SQL escape characters in user input from
130 | * causing unwanted side effects. (Failure to follow this convention may have security
131 | * implications.)
132 | *
133 | * Positional parameters are specified using the '?' character.
134 | *
135 | * Example:
136 | * This method may only be called once. If multiple tables are required, concatenate them
172 | * in SQL-format (typically comma-separated).
173 | *
174 | * If you need to do advanced {@code JOIN}s, they can also be specified here.
175 | *
176 | * See also: mapToTable()
177 | *
178 | * @param table Table name
179 | * @return Fluent interface
180 | */
181 | public SelectionBuilder table(String table) {
182 | mTable = table;
183 | return this;
184 | }
185 |
186 | /**
187 | * Verify that a table name has been supplied using table().
188 | *
189 | * @throws IllegalStateException if table not set
190 | */
191 | private void assertTable() {
192 | if (mTable == null) {
193 | throw new IllegalStateException("Table not specified");
194 | }
195 | }
196 |
197 | /**
198 | * Perform an inner join.
199 | *
200 | * Map columns from a secondary table onto the current result set. References to the column
201 | * specified in {@code column} will be replaced with {@code table.column} in the SQL {@code
202 | * SELECT} clause.
203 | *
204 | * @param column Column name to join on. Must be the same in both tables.
205 | * @param table Secondary table to join.
206 | * @return Fluent interface
207 | */
208 | public SelectionBuilder mapToTable(String column, String table) {
209 | mProjectionMap.put(column, table + "." + column);
210 | return this;
211 | }
212 |
213 | /**
214 | * Create a new column based on custom criteria (such as aggregate functions).
215 | *
216 | * This adds a new column to the result set, based upon custom criteria in SQL format. This
217 | * is equivalent to the SQL statement: {@code SELECT toClause AS fromColumn}
218 | *
219 | * This method is useful for executing SQL sub-queries.
220 | *
221 | * @param fromColumn Name of column for mapping
222 | * @param toClause SQL string representing data to be mapped
223 | * @return Fluent interface
224 | */
225 | public SelectionBuilder map(String fromColumn, String toClause) {
226 | mProjectionMap.put(fromColumn, toClause + " AS " + fromColumn);
227 | return this;
228 | }
229 |
230 | /**
231 | * Return selection string based on current internal state.
232 | *
233 | * @return Current selection as a SQL statement
234 | * @see #getSelectionArgs()
235 | */
236 | public String getSelection() {
237 | return mSelection.toString();
238 |
239 | }
240 |
241 | /**
242 | * Return selection arguments based on current internal state.
243 | *
244 | * @see #getSelection()
245 | */
246 | public String[] getSelectionArgs() {
247 | return mSelectionArgs.toArray(new String[mSelectionArgs.size()]);
248 | }
249 |
250 | /**
251 | * Process user-supplied projection (column list).
252 | *
253 | * In cases where a column is mapped to another data source (either another table, or an
254 | * SQL sub-query), the column name will be replaced with a more specific, SQL-compatible
255 | * representation.
256 | *
257 | * Assumes that incoming columns are non-null.
258 | *
259 | * See also: map(), mapToTable()
260 | *
261 | * @param columns User supplied projection (column list).
262 | */
263 | private void mapColumns(String[] columns) {
264 | for (int i = 0; i < columns.length; i++) {
265 | final String target = mProjectionMap.get(columns[i]);
266 | if (target != null) {
267 | columns[i] = target;
268 | }
269 | }
270 | }
271 |
272 | /**
273 | * Return a description of this builder's state. Does NOT output SQL.
274 | *
275 | * @return Human-readable internal state
276 | */
277 | @Override
278 | public String toString() {
279 | return "SelectionBuilder[table=" + mTable + ", selection=" + getSelection()
280 | + ", selectionArgs=" + Arrays.toString(getSelectionArgs()) + "]";
281 | }
282 |
283 | /**
284 | * Execute query (SQL {@code SELECT}) against specified database.
285 | *
286 | * Using a null projection (column list) is not supported.
287 | *
288 | * @param db Database to query.
289 | * @param columns Database projection (column list) to return, must be non-NULL.
290 | * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause (excluding the
291 | * ORDER BY itself). Passing null will use the default sort order, which may be
292 | * unordered.
293 | * @return A {@link Cursor} object, which is positioned before the first entry. Note that
294 | * {@link Cursor}s are not synchronized, see the documentation for more details.
295 | */
296 | public Cursor query(SQLiteDatabase db, String[] columns, String orderBy) {
297 | return query(db, columns, null, null, orderBy, null);
298 | }
299 |
300 | /**
301 | * Execute query ({@code SELECT}) against database.
302 | *
303 | * Using a null projection (column list) is not supported.
304 | *
305 | * @param db Database to query.
306 | * @param columns Database projection (column list) to return, must be non-null.
307 | * @param groupBy A filter declaring how to group rows, formatted as an SQL GROUP BY clause
308 | * (excluding the GROUP BY itself). Passing null will cause the rows to not be
309 | * grouped.
310 | * @param having A filter declare which row groups to include in the cursor, if row grouping is
311 | * being used, formatted as an SQL HAVING clause (excluding the HAVING itself).
312 | * Passing null will cause all row groups to be included, and is required when
313 | * row grouping is not being used.
314 | * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause (excluding the
315 | * ORDER BY itself). Passing null will use the default sort order, which may be
316 | * unordered.
317 | * @param limit Limits the number of rows returned by the query, formatted as LIMIT clause.
318 | * Passing null denotes no LIMIT clause.
319 | * @return A {@link Cursor} object, which is positioned before the first entry. Note that
320 | * {@link Cursor}s are not synchronized, see the documentation for more details.
321 | */
322 | public Cursor query(SQLiteDatabase db, String[] columns, String groupBy,
323 | String having, String orderBy, String limit) {
324 | assertTable();
325 | if (columns != null) mapColumns(columns);
326 | Log.v(TAG, "query(columns=" + Arrays.toString(columns) + ") " + this);
327 | return db.query(mTable, columns, getSelection(), getSelectionArgs(), groupBy, having,
328 | orderBy, limit);
329 | }
330 |
331 | /**
332 | * Execute an {@code UPDATE} against database.
333 | *
334 | * @param db Database to query.
335 | * @param values A map from column names to new column values. null is a valid value that will
336 | * be translated to NULL
337 | * @return The number of rows affected.
338 | */
339 | public int update(SQLiteDatabase db, ContentValues values) {
340 | assertTable();
341 | Log.v(TAG, "update() " + this);
342 | return db.update(mTable, values, getSelection(), getSelectionArgs());
343 | }
344 |
345 | /**
346 | * Execute {@code DELETE} against database.
347 | *
348 | * @param db Database to query.
349 | * @return The number of rows affected.
350 | */
351 | public int delete(SQLiteDatabase db) {
352 | assertTable();
353 | Log.v(TAG, "delete() " + this);
354 | return db.delete(mTable, getSelection(), getSelectionArgs());
355 | }
356 | }
357 |
--------------------------------------------------------------------------------
194 | * 2. For each item, check if it's in the incoming data.
195 | * a. YES: Remove from "incoming" list. Check if data has mutated, if so, perform
196 | * database UPDATE.
197 | * b. NO: Schedule DELETE from database.
198 | * (At this point, incoming database only contains missing items.)
199 | * 3. For any items remaining in incoming list, ADD to database.
200 | */
201 | public void updateLocalFeedData(final InputStream stream, final SyncResult syncResult)
202 | throws IOException, XmlPullParserException, RemoteException,
203 | OperationApplicationException, ParseException {
204 | final FeedParser feedParser = new FeedParser();
205 | final ContentResolver contentResolver = getContext().getContentResolver();
206 |
207 | Log.i(TAG, "Parsing stream as Atom feed");
208 | final List
49 | * SelectionBuilder builder = new SelectionBuilder();
50 | * Cursor c = builder.table(FeedContract.Entry.TABLE_NAME) // String TABLE_NAME = "entry"
51 | * .where(FeedContract.Entry._ID + "=?", id); // String _ID = "_ID"
52 | * .query(db, projection, sortOrder)
53 | *
54 | *
55 | *
56 | *
67 | * // String SESSIONS_JOIN_BLOCKS_ROOMS = "sessions "
68 | * // + "LEFT OUTER JOIN blocks ON sessions.block_id=blocks.block_id "
69 | * // + "LEFT OUTER JOIN rooms ON sessions.room_id=rooms.room_id";
70 | *
71 | * // String Subquery.BLOCK_NUM_STARRED_SESSIONS =
72 | * // "(SELECT COUNT(1) FROM "
73 | * // + Tables.SESSIONS + " WHERE " + Qualified.SESSIONS_BLOCK_ID + "="
74 | * // + Qualified.BLOCKS_BLOCK_ID + " AND " + Qualified.SESSIONS_STARRED + "=1)";
75 | *
76 | * String Subqery.BLOCK_SESSIONS_COUNT =
77 | * Cursor c = builder.table(Tables.SESSIONS_JOIN_BLOCKS_ROOMS)
78 | * .map(Blocks.NUM_STARRED_SESSIONS, Subquery.BLOCK_NUM_STARRED_SESSIONS)
79 | * .mapToTable(Sessions._ID, Tables.SESSIONS)
80 | * .mapToTable(Sessions.SESSION_ID, Tables.SESSIONS)
81 | * .mapToTable(Sessions.BLOCK_ID, Tables.SESSIONS)
82 | * .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS)
83 | * .where(Qualified.SESSIONS_BLOCK_ID + "=?", blockId);
84 | *
85 | *
86 | *
124 | * .where("blog_posts.category = 'PROGRAMMING');
125 | *
126 | *
127 | *
137 | * .where("blog_posts.title contains ?, userSearchString);
138 | *
139 | *
140 | * @param selection SQL where statement
141 | * @param selectionArgs Values to substitute for positional parameters ('?' characters in
142 | * {@code selection} statement. Will be automatically escaped.
143 | * @return Fluent interface
144 | */
145 | public SelectionBuilder where(String selection, String... selectionArgs) {
146 | if (TextUtils.isEmpty(selection)) {
147 | if (selectionArgs != null && selectionArgs.length > 0) {
148 | throw new IllegalArgumentException(
149 | "Valid selection required when including arguments=");
150 | }
151 |
152 | // Shortcut when clause is empty
153 | return this;
154 | }
155 |
156 | if (mSelection.length() > 0) {
157 | mSelection.append(" AND ");
158 | }
159 |
160 | mSelection.append("(").append(selection).append(")");
161 | if (selectionArgs != null) {
162 | Collections.addAll(mSelectionArgs, selectionArgs);
163 | }
164 |
165 | return this;
166 | }
167 |
168 | /**
169 | * Table name to use for SQL {@code FROM} statement.
170 | *
171 | *