1 /*
2  * Copyright (C) 2006 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 android.content;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.compat.annotation.UnsupportedAppUsage;
22 import android.os.Parcel;
23 import android.os.Parcelable;
24 import android.text.TextUtils;
25 import android.util.proto.ProtoOutputStream;
26 
27 import java.io.PrintWriter;
28 
29 /**
30  * Identifier for a specific application component
31  * ({@link android.app.Activity}, {@link android.app.Service},
32  * {@link android.content.BroadcastReceiver}, or
33  * {@link android.content.ContentProvider}) that is available.  Two
34  * pieces of information, encapsulated here, are required to identify
35  * a component: the package (a String) it exists in, and the class (a String)
36  * name inside of that package.
37  *
38  */
39 public final class ComponentName implements Parcelable, Cloneable, Comparable<ComponentName> {
40     private final String mPackage;
41     private final String mClass;
42 
43     /**
44      * Create a new component identifier where the class name may be specified
45      * as either absolute or relative to the containing package.
46      *
47      * <p>Relative package names begin with a <code>'.'</code> character. For a package
48      * <code>"com.example"</code> and class name <code>".app.MyActivity"</code> this method
49      * will return a ComponentName with the package <code>"com.example"</code>and class name
50      * <code>"com.example.app.MyActivity"</code>. Fully qualified class names are also
51      * permitted.</p>
52      *
53      * @param pkg the name of the package the component exists in
54      * @param cls the name of the class inside of <var>pkg</var> that implements
55      *            the component
56      * @return the new ComponentName
57      */
createRelative(@onNull String pkg, @NonNull String cls)58     public static @NonNull ComponentName createRelative(@NonNull String pkg, @NonNull String cls) {
59         if (TextUtils.isEmpty(cls)) {
60             throw new IllegalArgumentException("class name cannot be empty");
61         }
62 
63         final String fullName;
64         if (cls.charAt(0) == '.') {
65             // Relative to the package. Prepend the package name.
66             fullName = pkg + cls;
67         } else {
68             // Fully qualified package name.
69             fullName = cls;
70         }
71         return new ComponentName(pkg, fullName);
72     }
73 
74     /**
75      * Create a new component identifier where the class name may be specified
76      * as either absolute or relative to the containing package.
77      *
78      * <p>Relative package names begin with a <code>'.'</code> character. For a package
79      * <code>"com.example"</code> and class name <code>".app.MyActivity"</code> this method
80      * will return a ComponentName with the package <code>"com.example"</code>and class name
81      * <code>"com.example.app.MyActivity"</code>. Fully qualified class names are also
82      * permitted.</p>
83      *
84      * @param pkg a Context for the package implementing the component
85      * @param cls the name of the class inside of <var>pkg</var> that implements
86      *            the component
87      * @return the new ComponentName
88      */
createRelative(@onNull Context pkg, @NonNull String cls)89     public static @NonNull ComponentName createRelative(@NonNull Context pkg, @NonNull String cls) {
90         return createRelative(pkg.getPackageName(), cls);
91     }
92 
93     /**
94      * Create a new component identifier.
95      *
96      * @param pkg The name of the package that the component exists in.  Can
97      * not be null.
98      * @param cls The name of the class inside of <var>pkg</var> that
99      * implements the component.  Can not be null.
100      */
ComponentName(@onNull String pkg, @NonNull String cls)101     public ComponentName(@NonNull String pkg, @NonNull String cls) {
102         if (pkg == null) throw new NullPointerException("package name is null");
103         if (cls == null) throw new NullPointerException("class name is null");
104         mPackage = pkg;
105         mClass = cls;
106     }
107 
108     /**
109      * Create a new component identifier from a Context and class name.
110      *
111      * @param pkg A Context for the package implementing the component,
112      * from which the actual package name will be retrieved.
113      * @param cls The name of the class inside of <var>pkg</var> that
114      * implements the component.
115      */
ComponentName(@onNull Context pkg, @NonNull String cls)116     public ComponentName(@NonNull Context pkg, @NonNull String cls) {
117         if (cls == null) throw new NullPointerException("class name is null");
118         mPackage = pkg.getPackageName();
119         mClass = cls;
120     }
121 
122     /**
123      * Create a new component identifier from a Context and Class object.
124      *
125      * @param pkg A Context for the package implementing the component, from
126      * which the actual package name will be retrieved.
127      * @param cls The Class object of the desired component, from which the
128      * actual class name will be retrieved.
129      */
ComponentName(@onNull Context pkg, @NonNull Class<?> cls)130     public ComponentName(@NonNull Context pkg, @NonNull Class<?> cls) {
131         mPackage = pkg.getPackageName();
132         mClass = cls.getName();
133     }
134 
clone()135     public ComponentName clone() {
136         return new ComponentName(mPackage, mClass);
137     }
138 
139     /**
140      * Return the package name of this component.
141      */
getPackageName()142     public @NonNull String getPackageName() {
143         return mPackage;
144     }
145 
146     /**
147      * Return the class name of this component.
148      */
getClassName()149     public @NonNull String getClassName() {
150         return mClass;
151     }
152 
153     /**
154      * Return the class name, either fully qualified or in a shortened form
155      * (with a leading '.') if it is a suffix of the package.
156      */
getShortClassName()157     public String getShortClassName() {
158         if (mClass.startsWith(mPackage)) {
159             int PN = mPackage.length();
160             int CN = mClass.length();
161             if (CN > PN && mClass.charAt(PN) == '.') {
162                 return mClass.substring(PN, CN);
163             }
164         }
165         return mClass;
166     }
167 
appendShortClassName(StringBuilder sb, String packageName, String className)168     private static void appendShortClassName(StringBuilder sb, String packageName,
169             String className) {
170         if (className.startsWith(packageName)) {
171             int PN = packageName.length();
172             int CN = className.length();
173             if (CN > PN && className.charAt(PN) == '.') {
174                 sb.append(className, PN, CN);
175                 return;
176             }
177         }
178         sb.append(className);
179     }
180 
printShortClassName(PrintWriter pw, String packageName, String className)181     private static void printShortClassName(PrintWriter pw, String packageName,
182             String className) {
183         if (className.startsWith(packageName)) {
184             int PN = packageName.length();
185             int CN = className.length();
186             if (CN > PN && className.charAt(PN) == '.') {
187                 pw.write(className, PN, CN-PN);
188                 return;
189             }
190         }
191         pw.print(className);
192     }
193 
194     /**
195      * Helper to get {@link #flattenToShortString()} in a {@link ComponentName} reference that can
196      * be {@code null}.
197      *
198      * @hide
199      */
200     @Nullable
flattenToShortString(@ullable ComponentName componentName)201     public static String flattenToShortString(@Nullable ComponentName componentName) {
202         return componentName == null ? null : componentName.flattenToShortString();
203     }
204 
205     /**
206      * Return a String that unambiguously describes both the package and
207      * class names contained in the ComponentName.  You can later recover
208      * the ComponentName from this string through
209      * {@link #unflattenFromString(String)}.
210      *
211      * @return Returns a new String holding the package and class names.  This
212      * is represented as the package name, concatenated with a '/' and then the
213      * class name.
214      *
215      * @see #unflattenFromString(String)
216      */
flattenToString()217     public @NonNull String flattenToString() {
218         return mPackage + "/" + mClass;
219     }
220 
221     /**
222      * The same as {@link #flattenToString()}, but abbreviates the class
223      * name if it is a suffix of the package.  The result can still be used
224      * with {@link #unflattenFromString(String)}.
225      *
226      * @return Returns a new String holding the package and class names.  This
227      * is represented as the package name, concatenated with a '/' and then the
228      * class name.
229      *
230      * @see #unflattenFromString(String)
231      */
flattenToShortString()232     public @NonNull String flattenToShortString() {
233         StringBuilder sb = new StringBuilder(mPackage.length() + mClass.length());
234         appendShortString(sb, mPackage, mClass);
235         return sb.toString();
236     }
237 
238     /** @hide */
appendShortString(StringBuilder sb)239     public void appendShortString(StringBuilder sb) {
240         appendShortString(sb, mPackage, mClass);
241     }
242 
243     /** @hide */
244     @UnsupportedAppUsage
appendShortString(StringBuilder sb, String packageName, String className)245     public static void appendShortString(StringBuilder sb, String packageName, String className) {
246         sb.append(packageName).append('/');
247         appendShortClassName(sb, packageName, className);
248     }
249 
250     /** @hide */
251     @UnsupportedAppUsage
printShortString(PrintWriter pw, String packageName, String className)252     public static void printShortString(PrintWriter pw, String packageName, String className) {
253         pw.print(packageName);
254         pw.print('/');
255         printShortClassName(pw, packageName, className);
256     }
257 
258     /**
259      * Recover a ComponentName from a String that was previously created with
260      * {@link #flattenToString()}.  It splits the string at the first '/',
261      * taking the part before as the package name and the part after as the
262      * class name.  As a special convenience (to use, for example, when
263      * parsing component names on the command line), if the '/' is immediately
264      * followed by a '.' then the final class name will be the concatenation
265      * of the package name with the string following the '/'.  Thus
266      * "com.foo/.Blah" becomes package="com.foo" class="com.foo.Blah".
267      *
268      * @param str The String that was returned by flattenToString().
269      * @return Returns a new ComponentName containing the package and class
270      * names that were encoded in <var>str</var>
271      *
272      * @see #flattenToString()
273      */
unflattenFromString(@onNull String str)274     public static @Nullable ComponentName unflattenFromString(@NonNull String str) {
275         int sep = str.indexOf('/');
276         if (sep < 0 || (sep+1) >= str.length()) {
277             return null;
278         }
279         String pkg = str.substring(0, sep);
280         String cls = str.substring(sep+1);
281         if (cls.length() > 0 && cls.charAt(0) == '.') {
282             cls = pkg + cls;
283         }
284         return new ComponentName(pkg, cls);
285     }
286 
287     /**
288      * Return string representation of this class without the class's name
289      * as a prefix.
290      */
toShortString()291     public String toShortString() {
292         return "{" + mPackage + "/" + mClass + "}";
293     }
294 
295     @Override
toString()296     public String toString() {
297         return "ComponentInfo{" + mPackage + "/" + mClass + "}";
298     }
299 
300     /** Put this here so that individual services don't have to reimplement this. @hide */
writeToProto(ProtoOutputStream proto, long fieldId)301     public void writeToProto(ProtoOutputStream proto, long fieldId) {
302         final long token = proto.start(fieldId);
303         proto.write(ComponentNameProto.PACKAGE_NAME, mPackage);
304         proto.write(ComponentNameProto.CLASS_NAME, mClass);
305         proto.end(token);
306     }
307 
308     /**
309      * {@inheritDoc}
310      *
311      * <p>Two components are considered to be equal if the packages in which they reside have the
312      * same name, and if the classes that implement each component also have the same name.
313      */
314     @Override
equals(Object obj)315     public boolean equals(Object obj) {
316         try {
317             if (obj != null) {
318                 ComponentName other = (ComponentName)obj;
319                 // Note: no null checks, because mPackage and mClass can
320                 // never be null.
321                 return mPackage.equals(other.mPackage)
322                         && mClass.equals(other.mClass);
323             }
324         } catch (ClassCastException e) {
325         }
326         return false;
327     }
328 
329     @Override
hashCode()330     public int hashCode() {
331         return mPackage.hashCode() + mClass.hashCode();
332     }
333 
compareTo(ComponentName that)334     public int compareTo(ComponentName that) {
335         int v;
336         v = this.mPackage.compareTo(that.mPackage);
337         if (v != 0) {
338             return v;
339         }
340         return this.mClass.compareTo(that.mClass);
341     }
342 
describeContents()343     public int describeContents() {
344         return 0;
345     }
346 
writeToParcel(Parcel out, int flags)347     public void writeToParcel(Parcel out, int flags) {
348         // WARNING: If you modify this function, also update
349         // frameworks/base/libs/services/src/content/ComponentName.cpp.
350         out.writeString(mPackage);
351         out.writeString(mClass);
352     }
353 
354     /**
355      * Write a ComponentName to a Parcel, handling null pointers.  Must be
356      * read with {@link #readFromParcel(Parcel)}.
357      *
358      * @param c The ComponentName to be written.
359      * @param out The Parcel in which the ComponentName will be placed.
360      *
361      * @see #readFromParcel(Parcel)
362      */
writeToParcel(ComponentName c, Parcel out)363     public static void writeToParcel(ComponentName c, Parcel out) {
364         if (c != null) {
365             c.writeToParcel(out, 0);
366         } else {
367             out.writeString(null);
368         }
369     }
370 
371     /**
372      * Read a ComponentName from a Parcel that was previously written
373      * with {@link #writeToParcel(ComponentName, Parcel)}, returning either
374      * a null or new object as appropriate.
375      *
376      * @param in The Parcel from which to read the ComponentName
377      * @return Returns a new ComponentName matching the previously written
378      * object, or null if a null had been written.
379      *
380      * @see #writeToParcel(ComponentName, Parcel)
381      */
readFromParcel(Parcel in)382     public static ComponentName readFromParcel(Parcel in) {
383         String pkg = in.readString();
384         return pkg != null ? new ComponentName(pkg, in) : null;
385     }
386 
387     public static final @android.annotation.NonNull Parcelable.Creator<ComponentName> CREATOR
388             = new Parcelable.Creator<ComponentName>() {
389         public ComponentName createFromParcel(Parcel in) {
390             return new ComponentName(in);
391         }
392 
393         public ComponentName[] newArray(int size) {
394             return new ComponentName[size];
395         }
396     };
397 
398     /**
399      * Instantiate a new ComponentName from the data in a Parcel that was
400      * previously written with {@link #writeToParcel(Parcel, int)}.  Note that you
401      * must not use this with data written by
402      * {@link #writeToParcel(ComponentName, Parcel)} since it is not possible
403      * to handle a null ComponentObject here.
404      *
405      * @param in The Parcel containing the previously written ComponentName,
406      * positioned at the location in the buffer where it was written.
407      */
ComponentName(Parcel in)408     public ComponentName(Parcel in) {
409         mPackage = in.readString();
410         if (mPackage == null) throw new NullPointerException(
411                 "package name is null");
412         mClass = in.readString();
413         if (mClass == null) throw new NullPointerException(
414                 "class name is null");
415     }
416 
ComponentName(String pkg, Parcel in)417     private ComponentName(String pkg, Parcel in) {
418         mPackage = pkg;
419         mClass = in.readString();
420     }
421 
422     /**
423      * Interface for classes associated with a component name.
424      * @hide
425      */
426     @FunctionalInterface
427     public interface WithComponentName {
428         /** Return the associated component name. */
getComponentName()429         ComponentName getComponentName();
430     }
431 }
432